【VBA×WindowsAPI】FindWindow関数の使い方
FindWindow関数
FindWindow関数は指定したウィンドウ名(キャプション名)もしくはクラス名を持つウィンドウへのハンドルを取得するための関数です。
ウィンドウハンドルは、ウィンドウを識別するための特別な識別子です。
ウィンドウを開いたり閉じたり、移動させたり、サイズを変えたり、テキストを変更したりするなど、ウィンドウに対して行いたい様々な操作を指示する際に使用します。このウィンドウに対する操作もWindowsAPIに用意されている何種類もの関数で行うことが可能です。
ウィンドウハンドルはC++では「HWND型」という型で扱いますが、中身としては長整数型の値であるためVBAではLongPtr型(32bitではLong型)を使用します。VBAでウィンドウハンドルを格納する変数はC++の型に合わせてhWndなどと記載することが多いです。
ウィンドウハンドルは、ウィンドウが作成されるときに自動的に割り当てられます。
そして、そのウィンドウが存在している間は一貫して有効ですが、ウィンドウが閉じられると無効になります。つまりハンドルを取得すると毎回値が違うウィンドウも存在するということです。
VBAにおいてはUserFormもウィンドウであるため、たとえばUserFormを最前面に移動させたり、サイズを変更できるようにしたり、半透明にしたりと様々な操作を行うことが可能になります。このとき、その操作の対象となるUserFormへのハンドルを取得するための関数がFindWindow関数という訳です。この他にもGetActiveWindow関数やGetNextWindow関数、GetParent関数といったウィンドウハンドルを取得するための関数は何種類も存在します。
“どの条件で取得するか”という状況に合わせて柔軟に取得方法を変える必要がありますが、ウィンドウハンドルを取得する最も一般的な手法の一つがFindWindow関数です。
使用方法
FindWindow関数を使用するにはあらかじめ関数の宣言しておく必要があります。
※宣言をしないと関数は使えずにエラーとなるので書き忘れに注意しましょう。
使用しているWindowsが32bitか64bitかによって宣言時に書く文言が違います。
環境に合わせて以下のいずれかをコードの一番初め(Option Explicitの次の行あたり)に書いておくことで、そのモジュール内で各関数を使うことができるようになります。
Declare PtrSafe Function FindWindow Lib “user32” Alias “FindWindowA” _
(ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Declare Function FindWindow Lib “user32” Alias “FindWindowA” _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
上記のどちらを書けばいいかわからない場合は以下のコードをコピペして、モジュールの最上部に書いておきましょう。この構文を書いておくことで自動的に使うことのできる方の構文が使用されます。
VBE上では使えない方の構文が赤色で表示される場合がありますが、実行に影響はありません。
1 2 3 4 5 |
#If VBA7 Then Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr #Else Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long #End If |
各関数の宣言文は「Private/Public」を付けて各関数の有効範囲を指定することもできます。
・Public Declare PtrSafe Function~ :モジュール外で呼び出し有効
構文
FindWindow関数の構文は下記のように書きます。
hwnd = FindWindow(lpClassName, lpWindowName)
引数
lpClassName (String型)
ハンドルを取得したいウィンドウのクラス名を入力します。
クラス名(ウィンドウクラス名)とはウィンドウを区別するためにつけられている名前です。アプリケーションごとに違った名前がつけられているので、ハンドルを取得したいウィンドウが何のアプリケーションによって開かれているかをクラス名で指定します。
下記はVBA操作でよく利用されるアプリケーションのウィンドウクラス名です。
その他のアプリケーションのクラス名はGetClassName関数で調べることが可能です。
アプリケーション | ウィンドウクラス名 |
Excel | XLMAIN |
PowerPoint | PPTFrameClass |
Word | OpusApp |
メモ帳 | Notepad |
エクスプローラー | CabinetWClass |
UserForm | ThunderDFrame |
本引数にNULL値(vbNullString)を入力した場合、lpWindowNameで取得できる全てのウィンドウがハンドルの取得対象となります。lpWindowNameだけで確実に指定のウィンドウハンドルが取得できるという場合はvbNullStringを入力していても問題ありません。
lpWindowName (String型)
ハンドルを取得したいウィンドウのキャプション名を入力します。
UserFormもウィンドウ扱いのため、下記のように書くことでUserForm1のウィンドウハンドルを取得することが可能です。(これによりUserFormの可変化や透明化などの操作が行えます)
1 2 3 4 5 6 7 |
Sub Test() UserForm1.Show vbModeless hwnd = FindWindow(vbNullString, UserForm1.Caption) '<-lpWindowNameにUserFormのキャプション 'hwndを使った何らかの操作 End Sub |
NULL値(vbNullString)の場合、全てのウィンドウハンドルが取得対象となります。
戻り値
hwnd (64bit:LongPtr型 / 32bit:Long型)
戻り値は関数が成功した場合、引数で指定したクラス名とウィンドウ名を持つウィンドウへのハンドルです。(条件に当てはまるウィンドウが複数ある場合は、より前面にあるもの)
関数が失敗した場合の戻り値はNULL値(0)となります。
引数と同じく開発(実行)環境によって戻り値の型が変化するので注意が必要です。
ウィンドウの並び順は内部で情報として保持されており、最前面を切り替えるたびにリアルタイムで更新されています。これは基本的には画面上に表示されているとおりの並びです。
FindWindow関数はこの並び順の前面にあるものから優先して取得していきます。
たとえばlpClassNameに”XLMAIN”と入力している場合は、最も前面にあるExcelウィンドウのハンドルが取得できます。また、この優先順位はlpClassName, lpWindowNameのいずれもを”vbNullString”とした場合でも同じです。つまり両方の引数が”vbNullString”の場合は最前面にあるウィンドウのハンドルが取得できるということです。
サンプルコード
以下はFindWindow関数とGetWindowText関数のそれぞれを使って、特定のウィンドウのキャプション名を取得するサンプルコードです。Excelを開いている場合は、その中でも最前面にいるウィンドウのキャプションが取得できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As LongPtr Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr Sub main() Dim hwnd As LongPtr Dim lRet As LongPtr Dim sBuf As String * 100 Dim sCap As String 'Excelのウィンドウハンドルを取得 hwnd = FindWindow("XLMAIN", vbNullString) '取得したハンドルのキャプション名を取得(固定長文字列) lRet = GetWindowText(hwnd, sBuf, Len(sBuf)) '不要なバッファ分は削除 sCap = Left(sBuf, InStr(sBuf, vbNullChar) - 1) '取得したキャプション名をイミディエイトウィンドウに出力 Debug.Print sCap End Sub |
また、逆パターンとして以下はFindWindow関数とGetClassName関数を使って、特定のウィンドウのクラス名を取得するサンプルコードです。コマンドプロンプトを開いている状態で実行すると、コマンドプロンプトのクラス名を取得することができます。こちらのサンプルの場合はウィンドウをキャプション名から探索しているので、同名のウィンドウがより前面に存在した場合はそちらへのハンドルが取得されてしまうので注意が必要です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr Sub main() Dim hwnd As LongPtr Dim lRet As LongPtr Dim sBuf As String * 100 Dim sCls As String 'コマンドプロンプトのウィンドウハンドルを取得 hwnd = FindWindow(vbNullString, "コマンド プロンプト") '取得したハンドルのクラス名を取得(固定長文字列) lRet = GetClassName(hwnd, sBuf, Len(sBuf)) '不要なバッファ分は削除 sCls = Left(sBuf, InStr(sBuf, vbNullChar) - 1) '取得したクラス名をイミディエイトウィンドウに出力 Debug.Print sCls End Sub |
いずれもFindWindow関数で特定のウィンドウへのハンドルを取得できていることが確認できます。
VBA×WindowsAPIまとめページ
その他のWindowsAPI関数は下記ページにまとまっているので合わせて参照下さい。
参考
Microsoft公式:FindWindowA 関数 (winuser.h) – Win32 apps