【VBA×WindowsAPI】FindWindowEx関数の使い方
FindWindowEx関数
FindWindowEx関数は指定したウィンドウ内にある子ウィンドウへのハンドルを、クラス名やウィンドウ名から取得するための関数です。同じくWindowsAPIには名前の似たFindWindow関数という関数も存在しますが使い方や使用用途が微妙に違うので注意が必要です。
ウィンドウというとUserFormのようなダイアログボックス的なものをイメージすることが多いですが、実はそのウィンドウ内にあるボタンやテキストボックスなどもウィンドウとして扱われます。(UserFormでいうところのCommandButtonやTextBoxコントロールのイメージ)
たとえばVBE上でプロジェクトのプロパティを開いたときに表示されるウィンドウは下記のような構成になっています。どのウィンドウがどこを表しているのかは何となく見当がつくと思います。
このツリー構成は「Microsoft Spy++」を使って調べていますが、ここには[ウィンドウ][ウィンドウハンドル][ウィンドウ名][クラス名]の順ですべてのウィンドウ情報が表示されています。
ここに記載の[ウィンドウ名][クラス名]の情報から該当の[ウィンドウハンドル]の値を取得するための関数がFindWindowEx関数です。上画像の場合、”OK”という[ウィンドウ名]と”Button”という[クラス名]を使って、”000A0F8A”というOKボタンへの[ウィンドウハンドル]を取得することができます。
この子ウィンドウへのハンドルが取得できると、プログラム上でそのウィンドウの操作を行うことが可能になります。ウィンドウがボタンの場合はボタンを押したり、チェックボックスの場合はON/OFFを切り替えたり、テキストボックスの場合は値を入力できたりと様々な操作が可能になります。
(※これらの操作を行うためにはSendMessage関数やPostMessage関数などの関数を使います)
FindWindow関数はトップレベルのウィンドウ(いわゆる皆がイメージするウィンドウ)を取得することしかできないので、ウィンドウ内のウィンドウ(ボタンやテキストボックスなど)を取得したいときはこのFindWindowEx関数が必要となります。
使用方法
FindWindowEx関数を使用するにはあらかじめ関数の宣言しておく必要があります。
※宣言をしないと関数は使えずにエラーとなるので書き忘れに注意しましょう。
使用しているWindowsが32bitか64bitかによって宣言時に書く文言が違います。
環境に合わせて以下のいずれかをコードの一番初め(Option Explicitの次の行あたり)に書いておくことで、そのモジュール内で各関数を使うことができるようになります。
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
Declare Function FindWindowEx Lib “user32” Alias “FindWindowExA” _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, _
ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
上記のどちらを書けばいいかわからない場合は以下のコードをコピペして、モジュールの最上部に書いておきましょう。この構文を書いておくことで自動的に使うことのできる方の構文が使用されます。
VBE上では使えない方の構文が赤色で表示される場合がありますが、実行に影響はありません。
1 2 3 4 5 |
#If VBA7 Then 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 #Else Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long #End If |
各関数の宣言文は「Private/Public」を付けて各関数の有効範囲を指定することもできます。
・Public Declare PtrSafe Function~ :モジュール外で呼び出し有効
構文
FindWindowEx関数の構文は下記のように書きます。
hWnd = FindWindowEx(hWnd1, hWnd2, lpsz1, lpsz2)
引数
hWnd1 (64bit:LongPtr型 / 32bit:Long型)
検索する子ウィンドウの親ウィンドウのハンドルを入力します。
NULL値(0)を入力すると、デスクトップウィンドウが親ウインドウとして扱われます。
デスクトップウィンドウは全てのウィンドウの最も親となる存在であるため、この引数をNULL値(0)とした場合はトップレベルのウィンドウを検索するFindWindow関数と同じ扱いになります。
hWnd2 (64bit:LongPtr型 / 32bit:Long型)
検索する子ウィンドウの検索開始位置を示すウィンドウのハンドルを入力します。
該当のウィンドウハンドルを検索する順番はhWnd1の子ウィンドウ群のZオーダー(奥行き)順となりますが、この引数のウィンドウハンドルの次にあるウィンドウを検索の始まりとします。
NULL値(0)を入力すると、hWnd1の最初にある子ウィンドウが検索の始まりとなります。
※この引数はhWnd1の直接の子ウィンドウのハンドルである必要があります。
ウィンドウのZオーダーはSpy++で確認したときのツリー構造の上から順となります。
たとえば下の画像の場合、hWnd1に「000811A4」を、本引数に「0(NULL)」を入力すると最初の子供である「00071268」から「000E1046」までの11ウィンドウに対して上から順に検索が行われていきます。このとき、本引数に「0(NULL)」ではなく「05BF0804」を入力すると検索範囲をその次のウィンドウである「01200632」から「000E1046」までの5ウィンドウに絞ることができます。
lpsz1 (String型)
ハンドルを取得したいウィンドウのクラス名を入力します。
クラス名(ウィンドウクラス名)とはウィンドウを区別するためにつけられている名前です。クラス名はMicrosoftの公式ツール「Microsoft Spy++」や「Inspect.exe」で簡単に調べることができます。
NULL値(vbNullString)を入力した場合、lpsz2で取得できる全てのウィンドウがハンドルが検索の対象となります。lpz2の入力値だけで確実に指定のウィンドウハンドルが取得できるというような場合は本引数にvbNullStringを入力していても問題ありません。
lpsz2 (String型)
ハンドルを取得したいウィンドウのウィンドウ名を入力します。
こちらもクラス名と同様で「Microsoft Spy++」や「Inspect.exe」などのツールで調べることができます。ウィンドウ名はクラス名とは違い状況に応じて変更する場合もあるので注意が必要です。
NULL値(vbNullString)を入力した場合、lpsz1で取得できる全てのウィンドウがハンドルが検索の対象となります。lpz1の入力値だけで確実に指定のウィンドウハンドルが取得できるというような場合は本引数にvbNullStringを入力していても問題ありません。
戻り値
hWnd (64bit:LongPtr型 / 32bit:Long型)
戻り値は関数が成功した場合、引数で入力した情報をもとにした検索に引っかかったウィンドウへのハンドルです。(条件に当てはまるウィンドウが複数ある場合は、より前面にあるもの)
関数が失敗した場合の戻り値はNULL値(0)となります。
引数と同じく開発(実行)環境によって戻り値の型が変化するので注意が必要です。
FindWindowEx関数はこの並び順の前面にあるものから優先して取得していきます。たとえばlpzs1に”Button”と入力している場合は、最も前面にあるボタンへのハンドルが取得できます。
ウィンドウ名もクラス名も同じ子ウィンドウが複数存在する場合はこのZオーダーを考慮してhWnd2を入力することで、指定のウィンドウへのハンドルを確実に取得することができます。
サンプルコード
以下はFindWindowEx関数とFindWindow関数、SendMessage関数を使って、メモ帳に文字列を送信するサンプルコードです。メモ帳を開いた状態で下記コードを実行するとメモ帳に「VBAからメッセージ送信」という文字列が入力されます。メモ帳が複数開かれている場合はFindWindow関数で取得したメモ帳ウィンドウ(基本的にはより前面にあるメモ帳)に対して文字列が送信されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr 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 Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr Const WM_IME_CHAR As Long = &H286 Sub main() Dim hWnd As LongPtr Dim hWndEdit As LongPtr Dim sText As String Dim sChar As String 'メモ帳ウィンドウハンドル取得 hWnd = FindWindow("Notepad", vbNullString) If hWnd = 0 Then Call MsgBox("メモ帳を起動してください。", vbExclamation) Exit Sub End If 'メモ帳の文字入力エリアへのハンドル取得 hWndEdit = FindWindowEx(hWnd, 0, "Edit", vbNullString) '送信するメッセージ(改行コードも可) sText = "VBAからメッセージ送信" & vbLf '文字列を1文字ずつループ For i = 1 To Len(sText) '文字を送信用に変換 sChar = Asc(Mid(sText, i, 1)) '文字を送信 Call SendMessage(hWndEdit, WM_IME_CHAR, sChar, 0) Next i End Sub |
VBA×WindowsAPIまとめページ
その他のWindowsAPI関数は下記ページにまとまっているので合わせて参照下さい。
参考
Microsoft公式:FindWindowExW 関数 (winuser.h) – Win32 apps