ウインドウ操作に必要になるウインドウハンドルを子ウインドウも含めて全て取得したい
以前こちらの記事で(https://officevba.info/window-list/)ウインドウの一覧を取得するコードをご紹介しました。
このコードは階層構造になっている最上段のウインドウのみを取得するものだったのですが、ウインドウの内部をクリックしたり操作したりする際は子ウインドウや孫ウインドウなど下層にあるウインドウを順に取得しないといけないことも多く、一回で必要なすべてのウインドウの情報を取得することができませんでした。
子ハンドルや孫ハンドルを含めてウインドウハンドルを取得できない理由はウインドウの階層が一律でなく一般的なループ処理では対応できないことなのですが、以前フォルダとファイルを全て取得する処理で学んだ再帰処理を使うことで様々な階層のウインドウハンドルを全て取得できるようになりました。
今回は再帰処理を用いて開いているウインドウのすべてのハンドルを取得するコードをご紹介します。
ウインドウハンドルとは?
Windowsは開いたウインドウや、ウインドウに配置されているパーツなどそれぞれに一つずつにハンドルという数値型の値を割り当てて制御しているらしいです。
「ウインドウ」ハンドルと言ってもVBAでいうところのオブジェクトのような感じで、エクスプローラーの開いた画面のような見た目がウインドウのものから、テキストボックスやアイコン、ステータスバーなど色々なものに割り当てられていて、理論上はウインドウハンドルさえ取得できればWindowsAPIを用いて対象を自由に操作できるらしいです。(私自身が何でもできるわけではありませんのでらしいと記載しましたがかなり機能は豊富そうです。)
上記の通りWindowsAPIを用いて操作対象のウインドウを指定する際、このウインドウハンドルを指定することが大事なのですが、ウインドウハンドルはウインドウを開くたびにランダムに新しい数値が割り当てられるため、プログラムを実行する過程でウインドウハンドルを取得する手段がないとWindowsAPIでは操作ができないということになります。
スポンサーリンク
再帰処理とは?
再帰処理はプロシージャの中に自分自身を呼び出すコードを記載することで、ループ処理時の階層が一定でなくても各階層に同じ処理を行うことができることです。
よく使われるのはあるフォルダの中に様々な階層のフォルダが格納されていてそれぞれにファイルが格納されている際に、各階層を順に探ってファイルとフォルダの一覧を書き出したり、ファイルをコピーしたりするのに使われます。
ちなみに上記のような処理をしたい場合、Pythonの標準モジュールのglobを使えば引数を一つ指定するだけで再帰処理を実行することが可能です。
VBAではコードを書いて実装しないといけないですが書き方に慣れが必要なので、この辺りはPythonの方が新しい言語でより洗練されている気がします。
再帰処理を用いたウインドウハンドルを取得するVBAコードについて
ウインドウが階層構造になっていて、かつウインドウごとに一定の階層ではないというのが再帰で解決できるぴったりの環境で、以前ウインドウハンドルの一覧表を作成するコードを勉強していた時には再帰処理の書き方を知らなかったので実装できず、作成したコードはウインドウの最上位だけを取得するのみでした。
必要なウインドウハンドルを取得するため、必要な場合は一層下ったものを探る処理を何度も繰り返して取得していましたが、再帰処理を使えば一回で全部解決させることができますし、今回の以下のようなコードの書き方では階層の情報も把握できるのでかなり便利になります。
'ウインドウの中の次または前のウインドウハンドルを取得する
Declare Function GetNextWindow Lib "user32" _
Alias "GetWindow" _
(ByVal hWnd As Long, _
ByVal wFlag As Long) As Long
'ウインドウのキャプションタイトルを取得する
Declare Function GetWindowText Lib "user32" _
Alias "GetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String, _
ByVal cch As Long) As Long
'ウインドウが可視かどうかを取得する
Declare Function IsWindowVisible Lib "user32" _
(ByVal hWnd As Long) As Long
'ウインドウのクラス名を取得する
Declare Function GetClassName Lib "user32" _
Alias "GetClassNameA" _
(ByVal hWnd As Long, _
ByVal lpClassName As String, _
ByVal nMaxCount As Long) As Long
'ウインドウハンドルの子を取得する
Declare Function FindWindowEx Lib "User32.dll" _
Alias "FindWindowExA" ( _
ByVal hWndParent As Long, _
ByVal hwndChildAfter As Long, _
ByVal lpszClass As String, _
ByVal lpszWindow As String) As Long
'ウインドウハンドルを取得する
Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Public Const GW_HWNDLAST = 1
Public Const GW_HWNDNEXT = 2
Sub 再帰でWindow一覧のキャプションとクラスを取得する()
With ActiveSheet
.Cells(1, 1) = "ハンドル"
.Cells(1, 2) = "クラス名"
.Cells(1, 3) = "キャプション名"
End With
Dim k As Long: k = 2
Dim hWnd As Long: hWnd = FindWindow(vbNullString, vbNullString) '引数をvbNullStringにすると1つめのウインドウを取得する
Call ウインドウの取得recursion(hWnd, k)
End Sub
Sub ウインドウの取得recursion(ByRef hWnd As Long, ByRef k As Long)
Dim strClassNameCh As String * 100
Dim strCaptionCh As String * 80
Do
If IsWindowVisible(hWnd) Then
GetWindowText hWnd, strCaptionCh, Len(strCaptionCh)
GetClassName hWnd, strClassNameCh, Len(strClassNameCh)
Dim i As Long: i = ActiveSheet.UsedRange.Rows.Count + 1
With ActiveSheet
.Cells(i, 1).Value = hWnd
.Cells(i, k).Value = Left(strClassNameCh, InStr(strClassNameCh, vbNullChar) - 1)
.Cells(i, k + 1).Value = Left(strCaptionCh, InStr(strCaptionCh, vbNullChar) - 1)
End With
DoEvents
Dim hWndCh As Long: hWndCh = FindWindowEx(hWnd, 0, vbNullString, vbNullString)
Call ウインドウの取得recursion(hWndCh, k + 2)
End If
hWnd = GetNextWindow(hWnd, GW_HWNDNEXT)
Loop Until hWnd = GetNextWindow(hWnd, GW_HWNDLAST)
End Sub


コメント