2009年03月05日

最前面に表示するが、フォーカスは奪わない方法

『時報時計』を作る際に一番苦労したのは、画像表示の部分である。具体的に言うと、『フォーカスを奪わない』という仕様である。


 
開発当初、Form->Show()で表示して、Form->Hide()で隠していたのだが、これがなんだかあいまいで、最前面に来ることがあったり、起動時点での最前面に来たりした。また、フォーカスも、奪ったり奪わなかったりと、実にファジーな動作で頭を悩ませてくれた。Form->Visible = true; を用いても同様。

初期の仕様では、『上から2番目に表示する』という仕様にしていたのだが、SetActiveWindow(), SetForeGroundWindow(), BringWindowToTop(), SetWindowPos()のどれを用いても、2番目に来たり来なかったり、最前面に来たり来なかったり、起動時点での最前面に来たり来なかったり、とガッカリな動作。

そこで、仕様を変更して、『最前面に来るが、即座にフォーカスを(元いたフォーカスに)返す』という仕様にした。GetForegroundWindow()で、『時報時計』のForm->Show()が呼ばれた瞬間に最前面にいるアプリのハンドルを取得でき、そのハンドルを引数にSetForegroundWindow()に与えれば元に戻る、ということで、これでできたかと思ったのだが、これがエラーは出さないが正常に動作しないことがチョクチョクあるという、これまたファジーな挙動。基本的に俺用のソフトだったので、キー入力中(IME動作中)にフォーカスを奪うなど言語道断という考えで作っていたので、このファジーな挙動によって、時報時計にフォーカスが奪われるのは我慢ならなかった。

ふてくされて放置していたのだが、ふと、『Windows Live Messenger』の『メンバーがオンラインになったらアラートを表示する』はどうなっているのかが気になった。普段、鬱陶しいのでそんな機能はオフにしているのだが、これを見てみた。そしたら、『最前面に表示されるが、フォーカスは奪わない』という仕様だった。

俺が作っていたときも、それを目標に作っていたのだが、実際には『一瞬、時報時計がフォーカスを奪うが、すぐ元のアプリに返す』という挙動だった。この2つの違いは大きい。

これに気づいたら、あとは楽勝だった。少し調べると、SetWindowPos()の第7引数
UINT uFlags // ウィンドウ位置のオプション
には、SWP_NOSENDCHANGINGというフラグがある。MSDNの説明では、
ウィンドウに WM_WINDOWPOSCHANGING メッセージが送られないようにします。
と、何を言っているのか分からないことが書いてある。いちおう、CWnd::OnWindowPosChanging()などで更に説明があるが、読んでもサッパリ分からない。まぁ、頭の悪い説明や、俺の悪い頭は置いといて、とにかく、SetWindowPos()の第7引数にSWP_NOSENDCHANGINGを指定すると、フォーカスはそのままで、Zオーダーのみを最前面に渡すことができた。ただし、先にForm->Show()してしまうとマズいらしく、うまく動かない。
SetWindowPos( Form->Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOSENDCHANGING | SWP_SHOWWINDOW );
Form_Main->Visible = true;
とすれば、正常動作が望める。


ということで、キモはSWP_NOSENDCHANGINGだった。最前面に表示されるが、フォーカスは奪わない方法について言及している検索結果は少なかったので、TIPSとして、価値があるであろうと判断。ここに、載せておく。
posted by 小川 at 16:35| Comment(4) | TrackBack(0) | プログラム/マ | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
そのままズバリ探したもので助かりました、再描画のたびに呼び出していいのかはよくわかりませんが、とりあえずアクティブにならずに最前面を維持できるようになりました。
Posted by マロン at 2011年01月16日 23:07
いらっしゃいませ、おいでませー。
お役に立てて何よりです。
開発がんばってくださいねー。
Posted by 小川@管理人 at 2011年01月19日 00:09
ネットで色々調べていましてこちらへたどり着きました。
お世話になります。

VB.NET2010で
普段、VBのフォームを最小化しておいて
時間が経過すると(何かのタイミング)フォームを普通の状態にして
( FormWindowState.Normal)
PCモニターの右上にメッセージ表示するプログラムの作成を考えています。

職場で以下を記載しましたら
エラーになったのですが
環境が違ったでしょうか?

お手数おかけしますがよろしくお願いします。

SetWindowPos( Form->Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOSENDCHANGING | SWP_SHOWWINDOW );
Form_Main->Visible = true;


Posted by ほほ at 2017年09月21日 19:39
このような廃墟にようこそおいでませー。

私の書いたコードは、C++Builderのコードです。
VB.NETというと、Visual Basicですから、言語が異なります。
言語が違いますので、コードをそのまま流用することはできないでしょう。

「VB 最前面 フォーカス」とかで検索すれば、ヒットするかもしれません。
また、VBとC#のコードを併記して説明しているサイトも多いですから、「C# 最前面 フォーカス」とかで検索しても、VBの説明に辿りつけるかもしれません。

お力になれず申し訳ないですが、参考になれば。
Posted by 小川@管理人 at 2017年09月23日 10:46
コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック