2012年8月9日木曜日

[ExcelVBA] EnumWindow関数を使って楽にハンドルを取得したい


正直なところ毎回EnumWindow関数を書くのはしんどいので、関数化して簡単に利用できるようにしておきたい。
ということで、タイトル名を正規表現で渡して、一致する者のハンドルをキーに値にタイトルが入ったものが帰ってくる関数でも作ってみます。

段階を踏まえて書いてみます。
まずは、EnumWindowを使ってただハンドルをデバッグプリントしてみます。
  1. Option Explicit  
  2.   
  3. Declare Function EnumWindows Lib "user32.dll" (ByVal lpEnumFunc As Long, lParam As LongAs Long  
  4. Sub GetHWND()  
  5.     '必ずエラーになるので止まらないよう処理しておく  
  6.     On Error Resume Next  
  7.       
  8.     Dim Ret As Long  
  9.     Ret = EnumWindows(AddressOf EnumWindowsProc, 0)  
  10.       
  11.     On Error GoTo 0  
  12. End Sub  
  13.   
  14. Function EnumWindowsProc(ByVal HWND As LongAs Long  
  15.     EnumWindowsProc = 1  
  16.       
  17.     Debug.Print HWND  
  18. End Function  
  19.   
  20. Sub SampleCode()  
  21.     'コードを実行してみる  
  22.     Call GetHWND  
  23. End Sub  

3行目:トップレベルにあるウィンドウハンドルを探してくれるEnumWindow関数の宣言です。
6行目:ハンドルを全て見つけた後や、途中でやめた時にエラーとなるのでエラー処理をしておきます
9行目:EnumWindow関数を呼ぶ。
    第一引数はコールバック関数(ハンドルを見つけるたびにOSから呼出される関数)
    第二引数はコールバック関数に受け渡しができる整数の値。特に使う予定がないので0としてます
    戻り値は失敗したら0で成功したらそれ以外なんだと思うけど0しか見たことない…
11行目:エラー解除

コールバック関数側
15行目:戻り値を1(1:次のハンドルを探す,0:他のハンドルは探さず終了)
17行目:ハンドルを出力


次は、ハンドル名からウィンドウタイトルを出力してみます。

  1. Option Explicit  
  2.   
  3. Declare Function EnumWindows Lib "user32.dll" (ByVal lpEnumFunc As Long, lParam As LongAs Long  
  4. Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" _  
  5.     (ByVal HWND As LongByVal MSG As Long, _  
  6.      ByVal wParam As LongByVal lParam As LongAs Long  
  7. Declare Function SendMessageStr Lib "user32.dll" Alias "SendMessageA" _  
  8.     (ByVal HWND As LongByVal MSG As Long, _  
  9.      ByVal wParam As LongByVal lParam As StringAs Long  
  10. Const WM_GETTEXT = &HD  
  11. Const WM_GETTEXTLENGTH = &HE  
  12. Sub GetHWND()  
  13.     '必ずエラーになるので止まらないよう処理しておく  
  14.     On Error Resume Next  
  15.       
  16.     Dim Ret As Long  
  17.     Ret = EnumWindows(AddressOf EnumWindowsProc, 0)  
  18.       
  19.     On Error GoTo 0  
  20. End Sub  
  21.   
  22. Function EnumWindowsProc(ByVal HWND As LongAs Long  
  23.     EnumWindowsProc = 1  
  24.       
  25.     'ハンドルに該当する文字列数を取得(注:Null文字は含まない)  
  26.     Dim length As Integer  
  27.     length = SendMessageStr(HWND, WM_GETTEXTLENGTH, 0, 0)  
  28.     length = length + 1 'Null文字分1つ増やす  
  29.       
  30.     Dim str As String  
  31.     str = String(length, vbNullChar) 'Null文字分一つ多く領域を確保  
  32.       
  33.     Dim Ret As Integer  
  34.     Ret = SendMessageStr(HWND, WM_GETTEXT, length, str) 'Null文字も含め受取る  
  35.       
  36.     Dim WindowTitle As String  
  37.     WindowTitle = Left(str, Len(str) - 1)  
  38.       
  39.     Debug.Print WindowTitle  
  40. End Function  
  41.   
  42. Sub SampleCode()  
  43.     'コードを実行してみる  
  44.     Call GetHWND  
  45. End Sub  
変更点の説明
4行目:ウィンドウタイトルの文字数を取得する際のメッセージを送るためSendMessage関数を宣言
7行目:ウィンドウタイトルの文字列を取得する際のメッセージを送るためSendMessageStr関数を宣言
10行目:ウィンドウタイトルの文字列を取得するためのメッセージを設定
11行目:ウィンドウタイトルの文字数を取得するためのメッセージを設定
コールバック関数内
26行目:ウィンドウタイトルの文字数を取得するための変数を宣言
27行目:SendMessage関数にハンドル、文字数取得のメッセージ、第3、第4引数は未使用のため0を指定
    ハンドルからウィンドウタイトルの文字数を取得。
    文字列を取得する際に文字列の最後にNull文字がついてくるが
    ここでは、Null文字を除く文字数が帰ってくる。
28行目:文字列を取得する時Null文字もくっついてくるので1文字分多くしておく
30行目:文字列を受取るための変数を宣言
31行目:文字列+Null文字分に該当する文字数分、メモリを取得しておく
    ここでは、Null文字(vbNullChar)を指定。
34行目:SendMessageStr関数に
    ハンドル、文字列取得のメッセージ、受取る文字数、代入してもらう変数を指定
    ハンドルの文字列を取得
37行目:文字列+Null文字なのでNull文字分切り落とす
39行目:ハンドルの文字列を出力


最終的には、ウィンドウのタイトルを引数に渡すとそれに該当するハンドルと名前をDictionaryに格納して返してくれる関数にしましょう。
ウィンドウのタイトルはあやふやでも指定できるよう、正規表現で指定できることにしましょう。
  1. Option Explicit  
  2.   
  3. Declare Function EnumWindows Lib "user32.dll" (ByVal lpEnumFunc As Long, lParam As LongAs Long  
  4. Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" _  
  5.     (ByVal HWND As LongByVal MSG As Long, _  
  6.      ByVal wParam As LongByVal lParam As LongAs Long  
  7. Declare Function SendMessageStr Lib "user32.dll" Alias "SendMessageA" _  
  8.     (ByVal HWND As LongByVal MSG As Long, _  
  9.      ByVal wParam As LongByVal lParam As StringAs Long  
  10. Const WM_GETTEXT = &HD  
  11. Const WM_GETTEXTLENGTH = &HE  
  12. Dim RegWindowTitle As Object  
  13. Dim HWNDs As Object  
  14. Function GetHWND(ByVal WindowTitle As StringOptional IgnoreCase As Boolean = False)  
  15.     Set HWNDs = CreateObject("Scripting.Dictionary")  
  16.     Set RegWindowTitle = CreateObject("VBScript.RegExp")  
  17.     RegWindowTitle.Pattern = WindowTitle  
  18.     RegWindowTitle.Global = False  
  19.     RegWindowTitle.IgnoreCase = IgnoreCase  
  20.          
  21.     '必ずエラーになるので止まらないよう処理しておく  
  22.     On Error Resume Next  
  23.       
  24.     Dim Ret As Long  
  25.     Ret = EnumWindows(AddressOf EnumWindowsProc, 0)  
  26.       
  27.     On Error GoTo 0  
  28.       
  29.     Set GetHWND = HWNDs  
  30.     Set HWNDs = Nothing  
  31.     Set RegWindowTitle = Nothing  
  32. End Function  
  33.   
  34.   
  35. Function EnumWindowsProc(ByVal HWND As LongAs Long  
  36.     EnumWindowsProc = 1  
  37.   
  38.     'ハンドルに該当する文字列数を取得(注:Null文字は含まない)  
  39.     Dim length As Integer  
  40.     length = SendMessageStr(HWND, WM_GETTEXTLENGTH, 0, 0)  
  41.     length = length + 1 'Null文字分1つ増やす  
  42.       
  43.     Dim str As String  
  44.     str = String(length, vbNullChar) 'Null文字分一つ多く領域を確保  
  45.       
  46.     Dim Ret As Integer  
  47.     Ret = SendMessageStr(HWND, WM_GETTEXT, length, str) 'Null文字も含め受取る  
  48.       
  49.       
  50.     '文字列を出力(NULL文字分1つ削って出力)  
  51.     Dim WindowTitle As String, MatchWindowTitle As Object  
  52.     WindowTitle = Left(str, Len(str) - 1)  
  53.     Set MatchWindowTitle = RegWindowTitle.Execute(WindowTitle)  
  54.     If 0 < MatchWindowTitle.Count Then  
  55.         HWNDs.Add HWND, WindowTitle  
  56.     End If  
  57. End Function  
  58.   
  59.   
  60. Sub SampleCode()  
  61.     Dim HWNDs As Object, HWND As Variant  
  62.     Set HWNDs = GetHWND("電卓")  
  63.       
  64.     For Each HWND In HWNDs  
  65.         Debug.Print "[p]" & HWND & "," & HWNDs.Item(HWND)  
  66.     Next  
  67. End Sub  
変更点の説明 12行目:コールバック関数(EnumWindowsProc関数)とは直接変数のやり取りができないので     外で宣言して使いまわす。RegWindowTitleは正規表現用のオブジェクト 13行目:ハンドルをキーにウィンドウタイトルを格納するためのDictionaryオブジェクト 14行目:SubからFunctionに変更。     第一引数は、ウィンドウタイトルを表す正規表現     第ニ引数は、正規表現のIgnoreCaseのプロパティ値。     Optionaryなので明示的に指定しなくても可。その際デフォルトは、大文字小文字の区別はしない。 15行目:Dictionaryオブジェクトの生成 16行目:正規表現オブジェクトの生成 17行目:正規表現を指定 18行目:繰り返し探しださない。 19行目:大文字小文字の識別は第二引数次第。 29行目:戻り値を指定。オブジェクトなのでSetを使っている 30,31行目:オブジェクトの使用後の処理 コールバック関数内 53行目:正規表現に一致するか調べる。 54行目:マッチしたか調べる 55行目:マッチした時は、Dictionaryオブジェクトにハンドルをキーにウィンドウタイトルを値に格納する サンプルコードでは電卓というタイトルがあれば ハンドルとタイトルを取得することにしています。

0 件のコメント: