none
アクティブだったWindowのハンドルを求める方法 RRS feed

  • 質問

  • notifyIcon と contextMenuStrip を利用してタスクバーに表示されたアイコンからメニューを選択してアクティブだったWindowを操作するツールの作成を始めましたが最初で躓いてしまいました。

     "GetActiveWindow()" でタスクバーのメニューを操作する前にアクティブだったWindowのハンドルが取れるものと思っていたのですが、うまく動いてくれません。
    タスクバーのメニューを操作する前にアクティブだったWindowのハンドルが取れる方法をご教示下さい。

    開発環境
        C# 4.0, VS 2010
     

    2012年4月5日 8:10

回答

  • > "GetActiveWindow()" でタスクバーのメニューを操作する前にアクティブだったWindowのハンドルが取れるものと思っていたのですが、うまく動いてくれません。

    通知領域に表示されているアイコンを操作するためには、タスクバーがアクティブにならないとダメです。
    で、タスクバーもまたウィンドウなので、期待通りの動作は得られないことになります。

    >タスクバーのメニューを操作する前にアクティブだったWindowのハンドルが取れる方法をご教示下さい。

    グローバルをフックしかけて、アクティブウィンドウの履歴(過去数件?)をとっておくとかかな?
    • 回答としてマーク K.Ika 2012年4月23日 5:15
    2012年4月5日 10:50
    モデレータ

すべての返信

  • > "GetActiveWindow()" でタスクバーのメニューを操作する前にアクティブだったWindowのハンドルが取れるものと思っていたのですが、うまく動いてくれません。

    通知領域に表示されているアイコンを操作するためには、タスクバーがアクティブにならないとダメです。
    で、タスクバーもまたウィンドウなので、期待通りの動作は得られないことになります。

    >タスクバーのメニューを操作する前にアクティブだったWindowのハンドルが取れる方法をご教示下さい。

    グローバルをフックしかけて、アクティブウィンドウの履歴(過去数件?)をとっておくとかかな?
    • 回答としてマーク K.Ika 2012年4月23日 5:15
    2012年4月5日 10:50
    モデレータ
  • 直前にアクティブだったウィンドウハンドルを取得するAPIは、聞いたことがありません。GetActiveWindow()でシステム全体のアクティブウィンドウを常時監視して、自力で判断するというのも難しいですし・・・。

    もし相手のウィンドウも自分で作ったものであれば、ActivatedイベントとDeactivate イベントの組み合わせて2つのウィンドウの相互処理で対応可能か、検討されてみてはいかがでしょうか?

    2012年4月5日 10:57
  • 私も方法はないと思っています。

    もし、グローバルフックを考えるのであれば、C# だけでは作れませんので、ネイティブ DLL を作る必要があるととらえて検討してください。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。

    2012年4月5日 13:34
    モデレータ
  • マウス操作限定ならNotifyIconのMouseMoveイベントなんかでGetActiveWindow / GetForegroundWindowを実行しておけば、その時点でのアクティブなウィンドウを取得できそうですけど。

    #ためしにやってみたら、タスクバーや自分自身のハンドルを除外すればそれなりに正確に取れました。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2012年4月5日 14:10
  • この手の作業には CBT HOOK を使用するのが定石かもしれません。

    CBT フックはローカルフックなので、.NET C# 環境からも P/Invoke で仕掛けることができます。CBT フックを仕掛けて HCBT_ACTIVATE を記録することで、アクティブなウィンドウの切り替えを検知できるでしょう。(得られたウィンドウハンドルを多少加工したほうが便利なこともあります)

    2012年4月6日 0:21
  • みなさん参考になる情報 ありがとうございます。やはり、単純にはいかないのですね。

    ショートカットでのウィンドウ切替 ([Alt]+[Tab] ) 後、ショートカットで メニュー選択 の様なことも想定していますので CBT HOOK に挑戦してみようかと思います。

    頂いたキーワードで検索しているうちにZオーダー というものが目に入ってきました。
    実際に試していませんので全く見当違いかもしれませんが
    デスクトップのウィンドウをZオーダー 順に読み出し、非表示ウィンドウやダイアログ形式のウィンドウを除いた最初のウィンドウがアクティブウィンドウだったというのは無理があるでしょうか。

    2012年4月6日 1:06
  • ローカルフックの場合、別プロセスのウィンドウについてもアクティブになったことが、HCBT_ACTIVATEで通知されるのでしょうか? 通知されるのはフックを仕掛けたスレッドのウィンドウだけだと思っていました。
    2012年4月6日 1:57
  • ローカルフックの場合、別プロセスのウィンドウについてもアクティブになったことが、HCBT_ACTIVATEで通知されるのでしょうか? 通知されるのはフックを仕掛けたスレッドのウィンドウだけだと思っていました。

    その通りです。

    ローカルフックで HCBT_ACTIVATE が通知された時、それは自身がアクティブになろうとしているときです。このタイミングでは、まだ自身はアクティブになっていないので、OS に現在のアクティブウインドウを尋ねれば、目的の直前にアクティブだったウィンドウが手に入ります。

    2012年4月8日 5:32
  • 初歩的な質問で恐縮なのですが OS に現在のアクティブウインドウを尋ねル方法は GetForegroundWindow(); でよろしいでしょうか?

    自分のプログラム内のフォームが アクティブのとき以外は hWnd が 0 になってしまうのですが、やり方がまずいのでしょうか。

           private void button2_Click(object sender, EventArgs e)
            {
                if (hHook == 0)
                {
                    // Create an instance of HookProc.
                    WNDHookProcedure = new HookProc(Form1.WNDHookProc);
                    hHook = SetWindowsHookEx(WH_CBT,
                            WNDHookProcedure,
                        GetModuleHandle(null),
                            GetCurrentThreadId());

                }
            }

            public static int WNDHookProc(int nCode, IntPtr wParam, IntPtr lParam)
            {

                if (nCode == HCBT_ACTIVATE)
                {

                    hWnd = GetForegroundWindow();
                    int id;
                    GetWindowThreadProcessId(hWnd, out id);
                    Process process = Process.GetProcessById(id);
                    WndTxt = hWnd.ToString();
                    WndName = process.ProcessName;
                }
                return CallNextHookEx(hHook, nCode, wParam, lParam);
            }

            private void button3_Click(object sender, EventArgs e)
            {
                MessageBox.Show(WndName);

            }

    2012年4月9日 9:14
  • GetForegroundWindow, SetForegroundWindow は、三世代ぐらい前の Win95/NT3.51 時代の機能で、色々と制約を受けているのでうまく動かないかもしれませんね。

    GetGUIThreadInfo
    http://msdn.microsoft.com/ja-jp/library/cc364710.aspx

    とかでどうでしょう?

    ちなみに、テストコードなので省略されているのかもしれませんが、SetWindowHookEx に渡した CBT のコールバック用の delegate は GCHandle クラスを使って root 化するか、unhook するまでメンバ変数等に保存しておく必要があります。

    button2_click のような実装の場合、ボタンを押してフックを開始した後で作成された delegate をだれも保持していないため、GC によって delegate が回収されると二度とコールバックが呼ばれなくなります。(場合によっては、異常終了することもあります)

    2012年4月9日 14:20
  • Z-order はウィンドウの重なり合いの順序になります。たとえば、時計やニュース等を常に画面に表示しておく場合などに使われるもので、アクティブなウィンドウとは異なります。

    • アクティブなウィンドウ (アプリケーション毎に管理される)
    • 入力フォーカスを持ったウィンドウ (VIT 毎に管理される)
    • フォアグラウンドのウィンドウ (WindowStation 毎に管理される)
    • 前面にあるウィンドウ (z-order, 適当に変更できてOSの管理とあまり関係ない...)

    というのは、まったく違う概念なので、これらがすべて同じこともあれば、違うこともあります。

    2012年4月9日 14:29
  • その辺の概念を特に意識して考えていませんでした。

    ありがとうございました。

    2012年4月23日 4:47
  •  HCBT_ACTIVATE  では GetForegroundWindow, GetGUIThreadInfoどの方法でもハンドルは取れませんでした。

    最終的には  WH_MOUSE_LL と  WH_KEYBOARD_LL をHOOKし GetForegroundWindow() を利用して欲しい情報を取得することができました。

    2012年4月23日 4:57
  •  確認しました。エラー コード 87 で失敗します。

     なお、GetForegroundWindow や GetGUIThreadInfo をしなくても lParam に入ってくるとの事ですが、実際には 0 が入っていました。

    CBTProc より:

    wParam lPAram
    HCBT_ACTIVATE アクティブ化しようとしているウィンドウのハンドルを指定します。 1 個の 構造体へのロングポインタを指定します。この構造体で、アクティブウィンドウのハンドルと、マウスのクリックが原因でアクティブウィンドウの変化が発生したかどうかを示すフラグを指定します。

    Jitta@わんくま同盟

    2012年4月25日 12:20
  • >lParam に入ってくるとの事ですが、実際には 0 が入っていました。

    この辺は、 C# で利用しているからなのか、VSホスト・プロセスを有効で実行しているからなのか、等使用法の問題もきになっていましたが時間がなくなり検証できませんでした。

    2012年4月26日 5:54
  • >lParam に入ってくるとの事ですが、実際には 0 が入っていました。

    この辺は、 C# で利用しているからなのか、VSホスト・プロセスを有効で実行しているからなのか、等使用法の問題もきになっていましたが時間がなくなり検証できませんでした。


     CBT フックをかけたプロセスが所持するフォームとの間でフォーカス移動を行うと、ハンドルが入ってきました。他のプロセスとの間では、ハンドルは入ってきませんでした。vshost を通さず、コマンドツールから実行して確認しています。


    Jitta@わんくま同盟

    2012年4月26日 12:39
  • 追加情報ありがとうございました。
    2012年4月27日 1:48