【VBA×WindowsAPI】FindWindow関数の探索範囲を全階層に対応させる
Windows APIのFindWindow関数を使うと、指定したウィンドウのハンドルを取得できます。このハンドルを利用することで、ウィンドウを最前面に表示したり、透過させたりすることが可能です。この関数はウィンドウのクラス名とウィンドウ名を指定することで対象のウィンドウのハンドルを取得することができますが、その探索範囲は最上位のウィンドウ(一般的にいうウィンドウ)のみです。
指定のウィンドウの子ウィンドウはFindWindowEx関数を使うことで取得が可能ですが、その探索範囲は指定ウィンドウ直下の子供のみとなっています。このとき、たとえば10階層下にある子ウィンドウのハンドルを取得したい場合は10回もFindWindowEx関数を使用する必要があり、これは非常に煩わしいです。そこで本ページでは、FindWindowEx関数とその他のWindows API関数を組み合わせた、指定のウィンドウ配下のウィンドウを全階層探索するための関数について解説していきます。
全階層探索FindWindow関数
FindWindow関数の探索範囲を全階層にする方法は非常にシンプルです。FindWindow関数で取得できるのは最上位の親ウィンドウなので、基本的には子ウィンドウ探索が可能なFindWindowEx関数を使用します。このとき単純に子ウィンドウを探索するのではなく、再帰的に子ウィンドウを取得して指定のクラス名とウィンドウ名を持つウィンドウのハンドルを探索するだけです。
下記のWindows API関数を使うことで全階層を探索するFindWindow関数を作成することができます。それぞれの関数のより詳細な使い方の解説は各関数のリンクページを参照下さい。
FindWindowEx関数 :子ウィンドウのハンドルを取得する
GetClassName関数 :ウィンドウのクラス名を取得する
GetWindowText関数 :ウィンドウのウィンドウ名(タイトル)を取得する
子ウィンドウを探索する方法としてFindWindowEx関数のほかにEnumChildWindows関数なども用意されています。そのため次項に記載しているサンプルコードが唯一の方法というわけではありません。
サンプルコード
FindWindow関数の探索範囲を全階層させた関数は下記の通りです。
FindWindowDescendants関数を利用することで、第1引数で設定した親ウィンドウ配下の全てのウィンドウを探索して、指定のクラス名とウィンドウ名を持つウィンドウを取得します。再帰処理をしているため、取得した関数はByRefの第4引数で受け取ることになります。
Option Explicit Private Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr Private Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long Sub main() Dim hWnd As LongPtr '再帰処理で階層を無視してウィンドウハンドルを探索 Call FindWindowDescendants(0, "ClassName", "WindowName", hWnd) Debug.Print Hex(hWnd) End Sub Private Sub FindWindowDescendants(ByVal hWndParent As LongPtr, _ ByVal sClassName As String, _ ByVal sWindowName As String, _ ByRef hWndRet As LongPtr) Dim hwndChild As LongPtr Dim sBuf As String * 256 Dim sWin As String Dim sCls As String '指定のクラス名/ウィンドウ名のウィンドウが見つかり次第終了 If hWndRet <> 0 Then Exit Sub '最初の子ウィンドウを取得 hwndChild = FindWindowEx(hWndParent, 0, vbNullString, vbNullString) Do While hwndChild <> 0 'クラス名取得 sBuf = vbNullString Call GetClassName(hwndChild, sBuf, Len(sBuf)) sCls = Left(sBuf, InStr(sBuf, vbNullChar) - 1) 'ウィンドウ名取得 sBuf = vbNullString Call GetWindowText(hwndChild, sBuf, Len(sBuf)) If InStr(sBuf, vbNullChar) Then sWin = Left(sBuf, InStr(sBuf, vbNullChar) - 1) End If If sClassName = "" Then If sWindowName = sWin Then hWndRet = hwndChild End If ElseIf sWindowName = "" Then If sClassName = sCls Then hWndRet = hwndChild End If Else If sClassName = sCls And sWindowName = sWin Then hWndRet = hwndChild End If End If '再帰処理 Call FindWindowDescendants(hwndChild, sClassName, sWindowName, hWndRet) '次の子ウィンドウのハンドルを取得 hwndChild = FindWindowEx(hWndParent, hwndChild, vbNullString, vbNullString) Loop End Sub
ここではクラス名とウィンドウ名は、FindWindow関数と同様に完全一致を想定しています。ただし、上記の関数を少し変更すれば部分一致に対応させることも可能です。FindWindow関数を部分一致に対応させる方法については下記ページでまとめていますので、必要に応じて併せてご参照ください。
関連情報
VBA×WindowsAPIまとめページ
その他のWindowsAPI関数は下記ページにまとまっているので合わせて参照下さい。
参考
Microsoft公式:FindWindowA 関数 (winuser.h) – Win32 apps