Win32APIのEnumChildWindows
の列挙した結果を、広域変数を使用せずに取得する方法のメモです。
EnumChildWindows について
任意のウィンドウに対して、その子どものウィンドウを取得するための関数で、以下のようなシグネチャとなっています。
ref_EnumChildWindows
/* 返り値は使用されない。 /
BOOL EnumChildWindows(
HWND hWndParent / 親ウィンドウのハンドル。0 の場合は EnumWindows と同等。 /,
WNDENUMPROC lpEnumFunc / 各列挙で実行される関数へのポインタ。 /,
LPARAM lParam / 任意の追加情報。 */
);
https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-enumchildwindows
より引用(コメントは記事の作成者による)
`lpEnumFunc`で指定するコールバック関数`WNDENUMPROC`は、以下のようなシグネチャとなっています。
> ```cpp:ref_EnumChildProc
/* 列挙を続行する場合は TRUE を返す。 */
BOOL CALLBACK EnumChildProc(
_In_ HWND hwnd /* 見つかったウィンドウのハンドル。 */,
_In_ LPARAM lParam /* EnumChildWindows で指定された追加情報。 */
);
https://docs.microsoft.com/ja-jp/previous-versions/windows/desktop/legacy/ms633493(v=vs.85)
より引用(コメントは記事の作成者による)
よく見る使われ方と解決したい問題
上記のシグネチャからもわかるように、EnumChildWindows
では、直接列挙された結果を受け取れるのではなく、コールバック関数の方に結果が渡されるという構成になっています。
そのため、結果に対して何かしら処理をするために以下のような実装がされていることが多いです。
-
EnumChildProc
内に直接処理を書く - モジュール変数(広域変数)に結果を格納し、モジュール変数経由で受け取る
1の方法では、その時々に応じて中のコードを書き直す必要があり、やや再利用がしにくいです。
2の方法では、1に比べて再利用しやすいですが、広域変数を使っているのがなんとなく不安になるところです。
上記の問題を解決する方法として、lParam
の引数を使う、というのがこの記事の趣旨になります。
lParam
ですがポインタを示す引数となっているため、VBA では任意の変数をByRef
で渡したり、オブジェクトをByVal
で渡してもシグネチャ的に問題ありません。
ByRef
の場合はその変数へのポインタが関数に渡されますし、オブジェクトをByVal
で渡した場合はオブジェクト本体への参照アドレスが渡されるためです。
そのため、lParam
経由でVBA.Collection
などを渡せば、広域変数を介さず結果を取得することができます。
実装例
VBA7 以降を想定したコードです。
いまどき居ないとは思いますが、VBA6 以前を使用されている場合はLongPtr
をLong
に変更し、PtrSafe
を消してください。
'https://docs.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-enumchildwindows
Private Declare PtrSafe Function _
EnumChildWindows Lib "user32.dll" ( _
ByVal hWndParent As LongPtr, _
ByVal lpEnumFunc As LongPtr, _
ByVal lParam As VBA.Collection _
) As Long 'No use.
'https://docs.microsoft.com/ja-jp/previous-versions/windows/desktop/legacy/ms633498(v=vs.85)
Private Function EnumChildProc( _
ByVal hWnd As LongPtr, _
ByVal lParam As VBA.Collection _
) As Boolean 'If True Then Continue.
lParam.Add hWnd
Let EnumChildProc = True '列挙続行。
End Function
'inParentHwnd で指定したウィンドウの子ウィンドウのハンドルを格納した VBA.Collection を取得する。
'inParentHwnd を省略した場合はトップレベルウィンドウのハンドルを取得する。
Public Function GetChildWindows(Optional inParentHwnd As LongPtr = 0) As VBA.Collection 'Of LongPtr
Dim c As VBA.Collection 'Of LongPtr
Set c = New VBA.Collection
Set GetChildWindows = c
Call EnumChildWindows(inParentHwnd, AddressOf EnumChildProc, c)
End Function
参考
EnumChildWindows function (winuser.h) - Win32 apps | Microsoft Docs
EnumChildProc callback function (Windows) | Microsoft Docs