Edge自動操作 の記事の補足

以前書いた記事(Excel VBAでブラウザIEモードを操作するやつ)のサブルーチン部分の補足です。
備忘として残しておきます。
 【VBA】Edge IEモードを自動操作(インストール不要)|地獄の油揚げ (note.com)

コードは前掲の通りなので割愛。
サブルーチン部分のうち、コメントアウトしてある「Sub①、①ーA、①-B、①-C」の前半部分と、「Sub②」の後半部分に分けて見てみます。
前半部分の目的は、『ウィンドウクラス名が「InternetExplorer_Server」のウィンドウを探し出し、そのウィンドウハンドルを取得すること』。
後者部分の目的は、『取得した「InternetExplorer_Server」クラス名のウィンドウをIHTMLDocument2に変換して、DOM操作できるようにすること』。
こう考えると結構シンプルです。
何故クラス名が「InternetExplorer_Server」のウィンドウが必要なのかと言うと、これがIHTMLDocument2に変換できる情報を持ったウィンドウだからです。
「InternetExplorer_Server」クラス名のウィンドウは、Microsoft Edgeに属したウィンドウツリー(階層構造)の中に存在します。そしてEdgeをIEモードで開いた状態でないと確認できません。なので、必ずIEモードで対象ページを開いた状態で「InternetExplorer_Server」クラス名のウィンドウを探すことになります。
そもそもウィンドウって何?状態の人は、下記の記事を読んでからネット検索すれば、なんとなく理解できると思います。
 ウィンドウ操作の超基本的なこと|地獄の油揚げ (note.com)
当然ですが、ウィンドウには階層構造(親-子、所有ー被所有の関係)があります。

前半部分「Sub①、①ーA、①-B、①-C」の補足
EnumWindows関数を使って「現在開いている最上位ウィンドウ」を全て列挙し、配列Wnds()に格納しています。最上位のウィンドウとは、ウィンドウの階層構造におけるトップレベルに位置するウィンドウのことです。
関数の構文は「EnumWindows AdressOf コールバック関数, パラメータ」。
パラメータに0&を指定することで、最上位であるデスクトップ(デスクトップウィンドウ)から順にコールバック関数へウィンドウ情報(ここではウィンドウハンドル)を渡していきます。
全ての最上位ウィンドウがコールバック関数へ渡し終わるまで、EnumWindows関数は働きます。

コールバック関数は①-Aに書きました。
ここでは、ByVal hWndX As LongPtr にある通り第一引数でハンドル情報を受け取り、受け取ったハンドルを配列hWnd()の末尾に順次追加しています。
配列hWnd()はモジュール変数として宣言してあるので、コールバック関数①-Aの処理を抜けても情報保持されます。

①-Aの処理を抜けたら、再びSub①に処理が戻ります。
このとき、配列hWnd()には最上位ウィンドウのハンドルが詰まった状態です。GetWinTextプロシージャを呼びだして、このハンドルに対応するウィンドウキャプション名を1つづつ調べていきます。
引数Titleで指定した文字列(サンプルでは「google」)を含むウィンドウキャプションが発見できたら、そのハンドルを忘れないように変数hWndに代入します。
要はブラウザ(Microsoft Edge)の最上位ウィンドウを変数hWndに代入します。

GetWinTextプロシージャは①-Bに書きました。
受け取ったハンドルのキャプション名を返すだけのFunctionプロシージャですが、良く使うのでFunctionとして分けて書きました。
宣言に続けて*を付けると、*の後文字をポインタ型変数として扱うことができます。ポインタ型とはメモリ上のアドレスを記憶する変数のこと。

続いて、EnumChildWindows関数を使って、「指定したハンドルの子ウィンドウ」を順次コールバック関数へ渡し、列挙していきます。先ほどのEnumWindows関数と構文は同じですが、コールバック関数へ渡すハンドルの「子ウィンドウ」を列挙するところがポイントです。
コールバック関数EnumChildProcは①-Cに書きました。
①-Cでは ByVal hWndZ As LongPtr にある通り第一引数でハンドル情報を受け取り、受け取ったハンドルのクラス名を取得して「Internet Explorer_Server」と一致するかどうかをチェックしています。無事一致するものが見つかったら、モジュール変数IES_hWndに代入して、処理終了です。

後半部分「Sub②」の補足
RegisterWindowMessage関数でメッセージを定義し、SendMessageTimeout関数でメッセージを「InternetExplorer_Server」クラス名のウィンドウに送信します。
WM_GETOBJECTメッセージでは、アクセス可能なオブジェクトについての情報を得ることができます。
WM_HTML_GETOBJECTメッセ―ジを定義して送ると、HTMLDocumentを取得できます。
IHTMLDocument2を設定し、ObjectFromLresult関数でインターフェースポインターを取得します。ObjectFromLresult関数の第四引数に、WM_GETOBJECTメッセージに対応するオブジェクト上のインターフェースのアドレスが格納されます。つまり処理結果が第四引数の変数objEdgeに格納されます。
objEdgeもモジュール変数として宣言してあるのでSub②を抜けても情報保持されます。


以上、サブルーチン部分の補足おわり。
objEdgeにオブジェクト生成されれば、晴れてDOM操作可能です。
慣れ親しんだgetElementsByName等の操作ができます。
ただしInternetExplore時代によく使われていたコードのように
「Dim objIE As New InternetExplorer」でオブジェクト生成している訳では
ないので、「objIE.busy」や「objIE.Navigate」は使えません。
イメージとしては「objIE.document.○○」の様に、document階層でのコマンドが使える感じです。あくまでInternetExplorer型とは別物です。
ただし「objIE.busy」が使えなくたって、サンプルのメイン処理に書いたように、キャプション名の状態からページ読み込み状況を判断したりとやり様はあります。工夫次第ってところでしょうか。


因みにネット上の記事で、Sub①に相当するコードがありました。
あくまで自分用の備忘として残しておきますが、下記コードは残念ながら、私の職場環境では処理が遅くて使えませんでした。
あまり詳しくないので詳細不明ですが、クエリ条件でEdgeウィンドウを検索するところで手間取っていました。おそらく私の会社のせいで、1つのサーバーに複数人が一斉に接続して各自の仮想デスクトップ環境を構築している環境が原因だと思います。他のログインユーザーのウィンドウも含めてクエリ検索してしまったせいで処理が遅くなっているのではないか…
とにかくまあ、何かのお役に立てれば。

'Sub①の代案
Sub Get_Internet_Explorer_Server_hWnd_代案①()
    Dim con As Object
    Dim items As Object
    Dim pId As LongPtr: pId = 0
    Set con = CreateObject("WbemScripting.SWbemLocator").ConnectServer 'ローカルコンピューターに接続。
    hWnd = GetTopWindow(0)
        Do
           If GetParent(hWnd) = 0 Then
               GetWindowThreadProcessId hWnd, pId '第一引数hWndを作成したプロセスのプロセスIDを取得し、第二引数に処理結果を格納
               Set items = con.ExecQuery("Select * From Win32_Process Where ( ProcessId = '" & pId & "') And (Name = '" & ProcessName & "')") 'クエリ条件を指定し、Edgeのウィンドウかどうか判別
               If items.Count > 0 Then  'Edgeの子ウィンドウがあれば列挙
                   EnumChildWindows hWnd, AddressOf EnumChildProc, 0
                   If hIES <> 0 Then Exit Do
               End If
           End If
           hWnd = GetNextWindow(hWnd, GW_HWNDNEXT)  '次のウィンドウを取得'
        Loop While hWnd <> 0
    If hIES = 0 Then Exit Sub
End Sub


この記事が気に入ったらサポートをしてみませんか?