Win32 API でウィンドウを最前面にする方法

http://technet.microsoft.com/ja-jp/library/cc835597.aspx
によると、

最前面ウィンドウの扱い

概要

今まで作業していたウィンドウの代わりに、バックグラウンドに存在する、あるいはアイコン化しているウィンドウを最前面に表示する。これは、ユーザーに注意を促したり、アプリケーションの画面遷移を制御したりするため行われてきた方法です。しかし、この「ウィンドウを最前面に表示する」という操作は、必ずしもユーザーに便利な方法ではありません。

たとえば、メニューの深い階層をたどっているとき、勝手に他のウィンドウがフォアグランドになってしまうと、メニューは自動的に閉じられてしまいます。これにより、それまでのユーザーの操作が無効になってしまうため、Windows 98Windows 2000から、動作が変更されることになりました。この変更の結果、以下のような条件が適用されます。

*

プロセスがフォアグラウンドプロセスの場合、ウィンドウをフォアグラウンドに表示できます。
*

プロセスがフォアグラウンドプロセスによって開始されたばかりの場合、ウィンドウをフォアグラウンドにできます。
*

プロセス直前の入力を受け付けていた場合は、そのウィンドウをフォアグラウンドにできます。
*

その時点でフォアグラウンドウィンドウがない場合、ウィンドウをフォアグラウンドにできます。
*

フォアグラウンドプロセスのデバッグ中は、任意のウィンドウをフォアグラウンドにできます。
*

フォアグラウンドプロセスでタイムアウトロックが発生した場合 ( フォアグラウンドプロセスがしばらくの間何も行わず、応答していないような場合 ) 、ほかのウィンドウをフォアグラウンドにできます。
*

何らかのシステムメニューがアクティブの場合、アプリケーションはフォアグラウンドにはできません。

これらの条件を満たさないため、オペレーティングシステムが最前面ウィンドウを変更しないときは、代わりにタスクバーのボタンを点滅させます。ユーザーは、これを見て、アプリケーションに「呼ばれている」ことに気づきます。ここで、そのウィンドウを選ぶかどうかは、ユーザーの気持ち次第です。基本的に、最前面ウィンドウを変更できるのはユーザーのみ、ということになります。

テスト方法

Visual Basic であれば、AppActivate() ステートメントによって、この最前面表示を行います。しかし、この方法は、ほとんどの場合ウィンドウタイトルに頼らざるを得ないため、Win32 API の SetForegroundWindow() が使われるケースが多いことでしょう。これらの処理をコードの中で行っていないか確認するとともに、実際にアプリケーションを動作させて検証することになります。

検証する際は、他のアプリケーションで作業しているときや、システムメニューを操作しているときに、最前面ウィンドウの挙動がどうなるかを確認します。Windows XP の仕様どおりの動きとしては、タスクバーのボタンが点滅し、ユーザーへ通知が行われます。

回避方法

これまで見てきたように、この最前面ウィンドウの問題は Windows の仕様変更に起因しています。Windows の流儀にあわせて、最前面ウィンドウの変更は行わない、というのもひとつの考え方でしょう。もし、そういう選択を行うのなら、プログラムを修正する必要はありません。

それでもアプリケーションの要求として最前面ウィンドウを変更したいのであれば、Windows の流儀に反していることをきちんと理解した上で、修正したプログラムコードを実装しなくてはなりません。その方法ですが、いくつか考えられます。最前面ウィンドウ側のアプリケーションを修正できるのであれば、AllowSetForegroundWindow()API を利用して、他のアプリケーションに最前面ウィンドウ変更の許可を与えることができます。

最前面ウィンドウ側のアプリケーションが修正できない、他社製のアプリケーションであるといったときは、 AttachThreadInput()API を利用して、対象のアプリケーションの入力処理機構にアタッチし、その後に SetForegroundWindow() を呼び出すことになります。

サンプルコード - Visual Basic

' 自アプリケーションのスレッドIDを取得する lngTargetThreadID = _ GetWindowThreadProcessId( _ Me.hWnd, _ lngProcessID) ' 最前面アプリケーションのスレッドIDを取得する lngForegroundThreadID = _ GetWindowThreadProcessId( _ GetForegroundWindow(), _ lngProcessID) ' 最前面アプリケーションの入力処理機構に接続する lngResult = _ AttachThreadInput( _ lngTargetThreadID, _ lngForegroundTHreadID, _ 1) ' 最前面ウィンドウを変更する lngResult = _ SetForegroundWindow(Me.hWnd)

サンプルコード - Visual C++

int foregroundID; // 最前面プロセスのスレッドIDを取得する foregroundID = ::GetWindowThreadProcessId( ::GetForegroundWindow(), NULL); // 最前面アプリケーションの入力処理機構に接続する AttachThreadInput( ::GetCurrentThreadId(), foregroundID, TRUE); // 最前面ウィンドウを変更する ::SetForegroundWindow(this->m_hWnd);

だそうです。へええ。