ウィンドウを確実にアクティブにする方法
(背景) やりたいこと
ウィンドウハンドル hwnd に対し、activate(hwnd) を実行すると、hwnd が確実にアクティブ(Altabで選択した時の状態)になる……このような activate 関数を実装したい。
Windows 7 を想定。
答え
AttachThreadInput → BringWindowToTop をした後に、当該ウィンドウの左上をクリックする というやり方にした。
ソース(一部オレオレライブラリがあるけど割愛):
def activate_by_click(target_hwnd): rect = windowlib.GetWindowRect(target_hwnd) curmx, curmy = mouselib.get_cursorpos() mouselib.set_cursorpos(rect.xpos, rect.ypos) mouselib.left_click() # restore cursor pos. mouselib.set_cursorpos(curmx, curmy) def activate_by_attach(hwnd): import win32process import win32api import win32gui import ctypes forehwnd = windowlib.GetForegroundWindow() fore_threadid, processid = win32process.GetWindowThreadProcessId(forehwnd) current_threadid = win32api.GetCurrentThreadId() # foreground なスレッドにアタッチする if fore_threadid != current_threadid: try: # たまに error:87 が起きるので吸収. win32process.AttachThreadInput(current_threadid, fore_threadid, True) except: pass try: ctypes.windll.user32.BringWindowToTop(hwnd) except: pass if fore_threadid != current_threadid: try: win32process.AttachThreadInput(current_threadid, fore_threadid, False) except: pass def activete(target_hwnd): activate_by_attach(target_hwnd) activate_by_click(target_hwnd)
activate_by_attach() だけだとアクティブにならないケースがどうしても出てくる。原因もよーわからん。
そもそも Windows におけるウィンドウ制御では、あらゆるウィンドウのあらゆる状況を考慮して 100% 成功する挙動を実装するのは非現実的(*1)である。ある程度は諦めるしかないのだが、ふと「クリック使えばいいんじゃね?w」とひらめき、試したところ、かなり精度が良いので採用した。
activate_by_click() 関数として実装している。やってることはアクティブ対象ウィンドウの左上座標をゲットして、そこをクリックするだけ。ただしマウスカーソルが動いちゃうので、後で元に戻す処理も入れている。
- *1 Windows におけるウィンドウの諸性質は本質的に複雑であり(winapi を触ったことがあるなら関数の多さとパラメータの多さがわかるはずだ)、完全に把握できている人などこの世に一人もいないのではないかと思うほどである
参考
- 外部アプリケーションのウィンドウをアクティブにする: .NET Tips: C#, VB.NET
- 一番詳しい。様々なアプローチを解説している
- これらアプローチを全部盛り込んだサンプルもあるが『ここまでやってもウィンドウをフォアグラウンドできないこともある』とのこと
- ソーススクリプト - HSPのウィンドウを確実にアクティブにする
- AttachThreadInput からの SetForegroundWindow というやり方
- 指定したウィンドウ (HWND) を確実にアクティブにする | まくまく Windows ノート あるいは How to bring window to top with SetForegroundWindow() - CodeProject
- AttachThreadInput 前に Foreground Lock Timeout を 0 にするやり方
- Foreground Lock Timeout とは ユーザーが何かを入力した後、システムは一定の時間にわたって、アプリケーションが自らをフォアグラウンドにすることを禁止します
- 特定のプロセスをアクティブにする-3(ShowWindow, GetForegroundWindow, SetForegroundWindow, GetWindowThreadProcessId, BringWindowToTop, AttachThreadInput, PostMessage, WndProc, 最前面に表示) - いろいろ備忘録日記
- コメントがあってわかりやすい
- AttachThreadInput 後に BringWindowToTop を使うやり方
- TaskSwitchXP
- VistaSwitcher の前身となるソフトでソースも付いてる
- TaskSwitchXP_2.0.11-src\Source\TaskSwitchXP\TaskSwitchXP.cpp::TaskSwitchThread()::866L あたりだと思う
- AttachThreadInput と BringWindowToTop と SetForegroundWindow を使うやり方
おわりに
クリックは邪道だけど、今のところ、ほぼ確実にアクティブ化できているので良しとする。