【EXCEL VBA】Windowハンドルを取得して、ファイル選択ダイアログボックスを操作する

スポンサーリンク

はじめに

この前、「SendKeysでパソコンのキー操作を自動化する」っていう記事でダイアログボックスを操作する記事を書いたんですが、、、

動作が安定しないんで、面倒ではありますが結局ハンドルを捕まえに行くことになりました。。。

で、そのへんのことを紹介させて頂きます!w

スポンサーリンク

WindowとWindowハンドル

まずは、WindowとWindowハンドルについてです。

ウィンドウズ上で動いているいろんなアプリケーションは、基本的には”Window”と”コントロール”によって構成されています。

Windowは、アイコンクリックしたら、ビヨンって出てくる画面ですね。

コントロールは、その中のボタンとかみたいに理解するといいかもしれません。

それで、上の図のような感じで、各々のパーツが番号で管理されているというわけです。この番号をハンドルといいます。

図で「00000000」とか「99999999」とかの番号(ハンドル)は、Windowが作られた段階で作られるのでその都度変わります。

VBAから、このパーツを操作するためには、そのハンドルをその都度取得して捕まえる必要があるってことです。

スポンサーリンク

操作の方法

WindowsAPI関数

Windowハンドルを捕まえに行くためには、2つのステップがあります。

1つ目は、APIの宣言です。

そして2つ目はそのAPIの関数を使うことです。

これはセットですので覚えておいてください。

APIとは、Application Programming Interface (アプリケーション・プログラミング・インタフェース) の略で

、他の製品やプログラムにアプリケーションにアクセスするために用意されているプログラムです。

Windowは、Windowsの機能になります(別のプログラム)ので、VBAからWindowsのシステムにアクセスするためにはAPIを使う必要があります。

Windowsの機能にアクセスするためには、WindowsAPIというものが用意されています。

VBAから、WindowsAPIを利用するためには、VBAに対して、

「このAPI使うぞ!」

って最初に言っとかないといけないというわけです。

これがAPIの宣言です。

次の図は、Windowハンドルを取得するための”FindWindow関数”を使うための、WindowsAPIの宣言を書いた状態です。

ここで注意してもらいたいのは、APIの宣言は、プロシージャを記述する前の宣言セクションに記述する必要があることです。

図で”FindWindow関数”を使うためのと言いましたが、WindowsAPIは、使用したい関数ごとに宣言セクションに宣言を記述する必要があります。

めんどくさいですねw

我慢して下さい。

宣言セクションに、宣言が記述出来たら、通常通りプロシージャの中で宣言したWindowsAPI関数が使えるようになります。

ちなみに、Windowハンドルを取得する”FindWindow関数”は以下のような構文になっています。

ウインドウハンドル = FindWindow(“クラス名”, “キャプション名”)

クラス名は、どのウィンドウを取得するのかであらかじめ決まっています。以下に一部紹介しますが、他にもたくさんあります。

アプリケーション名ウィンドウクラス名
ExcelXLMAIN
WordOpusApp
AccessOMain
PowerPointPP10FrameClass
エクスプローラExploreWClass
メモ帳Notepad
ペイントMSPaintApp

キャプション名は、ウィンドウがポンと出たときに左上に出てくる題名です。

メモ帳だと次のような感じですね。

実施に、図のメモ帳のハンドルを取得してみます。

次のコードです。

’ API宣言
Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" ( _
        ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Sub sample()   
  Dim hmemo As Long ’変数宣言(ハンドルは番号なのでLong)
  hmemo = FindWindow("Notepad", "sample.txt - メモ帳")    '実際に取得しているコード
  Debug.Print hmemo ’イミディエイトウィンドウに出力
End Sub

※キャプションは、vbNullStringでもいけるっぽい。

番号はその都度変わってしまうので、これとは言えないですが、イミディエイトウィンドウに番号が表示されてばOKです。

ついでに、その捕まえたメモ帳に文字を入力してみましょう!

先ほど捕まえたのは、メモ帳の全体のハンドルなので、

まずは、文字を打ち込む場所のハンドルを捕まえる必要があります。

そして、そのあと文字を打ち込む場所に文字列を送るための機能も必要です。

めんどくさくなってきましたか?

我慢してくださいw

’ API宣言
Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" ( _
        ByVal lpClassName As String, _
        ByVal lpWindowName As String) As Long
' これ追加(編集部分を取得するために使う)
Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" ( _
        ByVal hwndParent As LongPtr, _
        ByVal hwndChildAfter As LongPtr, _
        ByVal lpClassName As String, _
        ByVal lpWindowName As String) As Long
        
' これ追加(編集部分に文字列を送るために使う)
Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" ( _
        ByVal hwnd As Long, _
        ByVal wMsg As Long, _
        ByVal wParam As Long, _
        lParam As Any) As LongPtr
Sub sample()
  
 ’メモ帳のハンドルを捕まえる
  Dim hmemo As Long
  hmemo = FindWindow("Notepad", "sample.txt - メモ帳")
  Debug.Print hmemo
  
  '捕まえたハンドル(Hmemo)から、Edit(文字を入れるとこ)のハンドルを捕まえる
  Dim hInputBox As Long
  hInputBox = FindWindowEx(hmemo, 0, "Edit", "") 
  Debug.Print hInputBox
  
  '捕まえたハンドル(Edit)に文字を送る
  Call SendMessage(hInputBox, &HC, 0, ByVal "さんぷる")
  
End Sub

まとめると、

  1. メモ帳のハンドルを捕まえる
  2. メモ帳のハンドルから編集するところを捕まえる
  3. 編集するところに文字列を送る

ってことです。

FindWindowsEx関数とSendMessage関数については、ここでは詳しく書きませんが、一個一個API宣言して関数を使うんだな~ということだけ覚えてください(^^♪

他にも、WindowsAPIには色々な関数がありますが、やると長くなりすぎるのでまた別の機会に。。。

パソコンの環境によって微妙に書き方が違ったりするので、これをコピペで動かない場合は、ご自身の環境に合わせていろいろググって探してみてください!(^^♪

ファイル選択ダイアログボックスを操作する

これが本題でしたので、ファイル選択ダイアログボックスの操作について紹介しますw

下の図のようなやつですね。

もうメモ帳でほとんどやったので、さっそくコードです。

’ ①API宣言
Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" ( _
        ByVal lpClassName As String, _
        ByVal lpWindowName As String) As Long
' これ追加(編集部分を取得するために使う)
Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" ( _
        ByVal hwndParent As LongPtr, _
        ByVal hwndChildAfter As LongPtr, _
        ByVal lpClassName As String, _
        ByVal lpWindowName As String) As Long
        
' これ追加(編集部分に文字列を送るために使う)
Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" ( _
        ByVal hwnd As Long, _
        ByVal wMsg As Long, _
        ByVal wParam As Long, _
        lParam As Any) As LongPtr
Sub sample()
  
 ’②ファイル選択ダイアログボックスのハンドルを捕まえる
  Dim handle As Long
  handle = FindWindow("#32770", "開く")
  
  '③捕まえたハンドル(handle)から入力ボックス(ファイル名が入るところ)のハンドルを捕まえる
  Dim hInputBox As Long
  hInputBox = FindWindowEx(handle, 0&, "ComboBoxEx32", "")
  hInputBox = FindWindowEx(hInputBox, 0&, "ComboBox", "")
  hInputBox = FindWindowEx(hInputBox, 0&, "Edit", "")
  Call SendMessage(hInputBox, &HC, 0, ByVal "***ファイルPATH**")
 
 ’④捕まえたハンドル(handle)から”開く”ボタンのハンドルを取得する
 Dim hButton As Long
 hButton = FindWindowEx(handle, 0&, "Button", "開く(&O)") '開くボタン
  Call SendMessage(hButton, &HF5, 0, 0&) 'ボタンをクリックする
End Sub

みたいな感じになります。

①は同じですね。

②は、ファイ選択ダイアログボックスのクラス名は「♯32770」になるので注意してください。

③ファイル名を入れるところに、ファイルPATHを入れる必要がありますが、何階層かになっているので順番に取得していきます。

最後に、SendMessageでファイルPATHを送信です。

④ファイルを確定するには、通常”開く”ボタンを押しますね。

開くボタンを取得して、SendMessage関数を使ってボタン押下の命令を出します。

SeleniumBasicと合わせてWEBアプリを操作

SeleniumBasicでブラウザ操作しながら、ファイルのアップロードなどでダイアログボックスが出てきたらWindowsAPIで操作

みたいな感じで、WEBアプリケーションを操作出来ます!

まとめ

SendKeysでずぼらかまそうとしましたが、やっぱりちゃんとやらんといかんですねw

という勉強になりました(^^♪

安定感抜群で動いてくれますよ。

その他 Seleniumに関する記事

コメント

  1. バイソン より:

    こんにちは。UIAutomationを勉強中です。内容が非常にわかりやすくて理解がすすみました。ありがとうございます。
    さて、もう一つご教授いただきたいことがあります。

    メモ帳のメニューFをクリックして、ファイル選択ダイアログボックスを開くまでの操作部分のプログラムがうまくいきません。
    よろしければソースを公開くださるようお願いいたします。

    • hirachin より:

      バイソンさん
      コメントありがとうございます!
      励みになります\(^o^)/

      さて、ご質問の件なのですが、以下確認させて下さいm(_ _)m
      メニューFというのは、「ファイル(F)」のボタンのことでしょうか?
      その後のファイル選択ダイアログボックスというのは、新しいファイルを開くための「開く(O)」ボタンになりますか?

      私の理解力がなくて申し訳無いですが宜しくお願いします!

      • バイソン より:

        お世話になります。hirachinさんのご理解のとおりです。

        「ファイル(F)」をクリックすると現れるメニュー表から「開く(O)」を選ぶプロセスとなります。
        よろしくお願いいたします。

        • hirachin より:

          バイソンさん
          返信ありがとうございます!

          やったこと無かったので調べてやってみました!
          =========================
          ‘ API宣言
          Declare PtrSafe Function FindWindow Lib “user32” Alias “FindWindowA” ( _
          ByVal lpClassName As String, _
          ByVal lpWindowName As String) As Long

          Declare PtrSafe Function FindWindowEx Lib “user32” Alias “FindWindowExA” ( _
          ByVal hwndParent As LongPtr, _
          ByVal hwndChildAfter As LongPtr, _
          ByVal lpClassName As String, _
          ByVal lpWindowName As String) As Long

          Declare PtrSafe Function SendMessage Lib “user32” Alias “SendMessageA” ( _
          ByVal hwnd As Long, _
          ByVal wMsg As Long, _
          ByVal wParam As Long, _
          lParam As Any) As LongPtr

          ‘ これ追加しています★
          Const WM_COMMAND As Long = &H111 ‘0x0111 メニューアイテムの選択・コントロールからの通知

          Sub sample()

          ‘メモ帳のハンドルを捕まえる
          Dim hmemo As Long
          hmemo = FindWindow(“Notepad”, “無題 – メモ帳”)

          ‘メニューの2番目(新規:0 新しいウィンドウ:1 開く:2)
          Dim res As Long
          res = SendMessage(hmemo, WM_COMMAND, 2, 0)

          End Sub
          =============================
          こんな感じでどうでしょう?

          捕まえたメモ帳のハンドルに対して、SendMessageで操作します。
          SendMessageって言う名前が違和感ありますね!
          コマンドを送るって捉えたら良さそうです(^^)

          他にも、このサイトでコマンド調べたら色々出来そうです!
          https://chokuto.ifdef.jp/urawaza/message/index.html

          コードが16進数で「&H~」みたいな感じに、定数指定してます。

          • バイソン より:

            お忙しいところ、アドバイスをいただき、感謝しております。

            コピペして動かしたところ、
            コンパイルエラー:型が一致しませんとエラーメッセージがありました。

            Dim res As Longのところを Dim res As LongPtrにしたら大丈夫でした。

            本当にありがとうございました。

            ちなみに、SendMessageを使わずに、UIAutomationを使ったら
            どんな感じになるのか、おわかりですか?

          • hirachin より:

            すみません、環境まで気が回ってませんでした。。。m(_ _)m
            でもお役に立てたなら何よりです!

            ちなみに、SendMessageを使わずに、UIAutomationを使ったら
            どんな感じになるのか、おわかりですか?
            についてですが、UIAutomationを使ったことがなくぱっと答えられなくて申し訳ないです。。。

            ちょこっとググって見たところ以下の記事を見つけました!
            https://yaromai.jp/vba-uiautomation/

            簡単には、
            1.Windowハドルを捕まえる
            2.そのハンドルに紐づくボタンとかのエレメントを取得する
            3.順番に操作したいエレメントを探す
              ※ここは、Windowの構造によっては何回か繰り返す必要あり
            4.取得したエレメントに指定の動作をさせる
            みたいなイメージですね!

            もう少し具体的に調べてみます(^^)

  2. […] 【EXCEL VBA | ノート】Windowハンドルを取得して、ファイル選択ダイアログボックスを操作する 投稿日: 2022年3月17日2022年3月17日作成者 y-uechiカテゴリー VBA関連 […]

  3. […] 【EXCEL VBA | ノート】Windowハンドルを取得して、ファイル選択ダイアログボックスを操作する 投稿日: 2022年3月17日作成者 y-uechiカテゴリー 未分類 […]

  4. たろ より:

    (ファイル選択ダイアログボックスを操作する)でファイルパスを変数にしたいのですが、どうすれば良いか教えてください。

    • hirachin より:

      @たろさん

      コメントありがとうございます!


      Sub sample()
       ’②ファイル選択ダイアログボックスのハンドルを捕まえる
      Dim handle As Long
      handle = FindWindow("#32770", "開く")

      '③捕まえたハンドル(handle)から入力ボックス(ファイル名が入るところ)のハンドルを捕まえる
      Dim hInputBox As Long
      hInputBox = FindWindowEx(handle, 0&, "ComboBoxEx32", "")
      hInputBox = FindWindowEx(hInputBox, 0&, "ComboBox", "")
      hInputBox = FindWindowEx(hInputBox, 0&, "Edit", "")
      Call SendMessage(hInputBox, &HC, 0, ByVal "*ファイルPATH")
       
       ’④捕まえたハンドル(handle)から”開く”ボタンのハンドルを取得する
       Dim hButton As Long
       hButton = FindWindowEx(handle, 0&, "Button", "開く(&O)") '開くボタン
      Call SendMessage(hButton, &HF5, 0, 0&) 'ボタンをクリックする

      End Sub

      これの


      Call SendMessage(hInputBox, &HC, 0, ByVal "*ファイルPATH")

      の部分でしょうか?


      Dim path As String: path = "*ファイルPATH"
      Call SendMessage(hInputBox, &HC, 0, ByVal path)

      にしてはいかがでしょう?

タイトルとURLをコピーしました