2012年8月8日水曜日

[ExcelVBA] EnumWindows


■説明
画面上のすべてのトップレベルウィンドウを列挙する関数。
ウィンドウのハンドルを見つけるとそれがトリガーとなり
指定した関数を呼び出します。
指定した関数は、戻り値が1の時だけ次のハンドルを探すプロセスに移ります。

この関数が終了する時は、トップレベルのウィンドウハンドルを全て見つけた時と
指定した関数が0を返した時になります。
いずれもエラーを起こすのでエラー処理が必要となります。



■宣言
Declare Function EnumWindows Lib "user32.dll" (ByVal lpEnumFunc As Long, lParam As Long) As Long
lpEnumFunc:ハンドルを見つけた際にハンドルを受け渡す関数名を指定します。
(関数の指定には、AddressOf演算子を使います)
lParam :指定した関数に渡すアプリケーション定義の値を指定
戻り値  :関数が成功すると、0 以外の値が返ります。
※定義としては、上の通りですが0以外の値を見たことがない…

■サンプルコード
まずは、APIの宣言とEnumWindows関数の使用とハンドルが見つかった際に呼び出す関数を作成します
Option Explicit

Declare Function EnumWindows Lib "user32.dll" (ByVal lpEnumFunc As Long, lParam As Long) As Long
Sub SampleCode()
    Dim Ret As Long
    Ret = EnumWindows(AddressOf hogehoge, 0)

    Debug.Print Time
End Sub

Function hogehoge(ByVal hwnd As Long) As Long
    Debug.Print hwnd
End Function

呼び出される関数名をhogehgoeにしてみたので
AddressOf hogehogeとしています。
第二引数は、hogehoge内のデフォルトの値だと
思うのですが何を指定しても変化がないようです…


さて、F8で一つずつ実行していくとEnumWindows関数が一つ目のハンドルを見つけて
hogehoge関数を呼びだし、ハンドルを引数に渡しているのがわかると思います。
その結果、デバッグプリントしています。
ただし、その後関数を抜けるとエラーとなります。

ハンドルを見つけて呼び出した関数の戻り値が1でない限り
エラーを吐いて終了するからです。

では、エラー処理を追加してみます。
まだまだ問題あり。
Option Explicit

Declare Function EnumWindows Lib "user32.dll" (ByVal lpEnumFunc As Long, lParam As Long) As Long
Sub SampleCode()
    On Error Resume Next 'エラーがあっても止めない
    Dim Ret As Long
    Ret = EnumWindows(AddressOf hogehoge, 0)
    On Error GoTo 0 '問題の部分を過ぎたのでエラー処理をもとに戻す
    Debug.Print Time
End Sub

Function hogehoge(ByVal hwnd As Long) As Long
    Debug.Print hwnd
End Function

今度は、一つのハンドルをデバッグプリントした後
エラー処理をしているので、正しく処理が流れています。

ただ、電卓アプリを立ち上げていた時そのハンドルを取得したい
といった場合、該当するアプリのハンドルを見つけるまでは
次々ハンドルを見つけないといけないのでhogehoge関数は
戻り値として1を返すことにしましょう。

Option Explicit

Declare Function EnumWindows Lib "user32.dll" (ByVal lpEnumFunc As Long, lParam As Long) As Long
Sub SampleCode()
    '必ずエラーになるので止まらないよう処理しておく
    On Error Resume Next 
    Dim Ret As Long
    Ret = EnumWindows(AddressOf hogehoge, 0)
    On Error GoTo 0 '問題の部分を過ぎたのでエラー処理をもとに戻す
    Debug.Print Time
End Sub

Function hogehoge(ByVal hwnd As Long) As Long
    hogehoge = 1
    Debug.Print hwnd
End Function

これでトップレベルにあるウィンドウのハンドルを全て
デバッグプリントすることができるようになりました。

ただし、ハンドル(数字)の羅列のみだと目的のウィンドウのハンドルがどれかわからないので
ハンドルを元にウィンドウのタイトル名を探すことをしないといけません。
処理を追加します。
Option Explicit

Declare Function EnumWindows Lib "user32.dll" (ByVal lpEnumFunc As Long, lParam As Long) As Long

Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" _
    (ByVal hWnd As Long, ByVal MSG As Long, _
     ByVal wParam As Long, ByVal lParam As Long) As Long

Declare Function SendMessageStr Lib "user32.dll" Alias "SendMessageA" _
    (ByVal hWnd As Long, ByVal MSG As Long, _
     ByVal wParam As Long, ByVal lParam As String) As Long
Const WM_GETTEXT = &HD
Const WM_GETTEXTLENGTH = &HE

Sub SampleCode()
    '必ずエラーになるので止まらないよう処理しておく
    On Error Resume Next
    Dim Ret As Long
    Ret = EnumWindows(AddressOf EnumWindowsProc, 0)
    On Error GoTo 0 '問題の部分を過ぎたのでエラー処理をもとに戻す
    Debug.Print Time
End Sub

Function EnumWindowsProc(ByVal hWnd As Long) As Long
    EnumWindowsProc = 1
    
    'ハンドルに該当する文字列数を取得(注:Null文字は含まない)
    Dim length As Integer
    length = SendMessageStr(hWnd, WM_GETTEXTLENGTH, 0, 0)
    length = length + 1 'Null文字分1つ増やす
    
    Dim str As String
    str = String(length, vbNullChar) 'Null文字分一つ多く領域を確保
    
    Dim Ret As Integer
    Ret = SendMessageStr(hWnd, WM_GETTEXT, length, str) 'Null文字も含め受取る
    
    '文字列を出力(NULL文字分1つ削って出力)
    Debug.Print Left(str, Len(str) - 1)
End Function
ちなみに、hogehogeからEnumWIndowsProcに名前を変えました。
特に意味はないです…

文字を出力する関係で
SendMessage/SendMessageStr/WM_GETTEXT/WM_GETTEXTLENGTHを追加しました。

0 件のコメント: