VBAでファイル選択ダイアログを自動操作する


色々なシーンで見かける以下の様な「ファイル選択ダイアログ」ですが、これを自動操作できれば便利だと思いませんか?

今回はこのダイアログを自動操作して、指定したファイルを選択させる処理を紹介致します。

ウィンドウとウィンドウハンドルについて

ダイアログの操作にあたって、まずは「ウィンドウ」というものが何かを理解する必要があります。

Windows上で動作している様々なアプリケーションは「ウィンドウ」と「コントロール」の組み合わせで構成されています。Microsoftでは以下の様に紹介されています。

Windows では、さまざまな形式のウィンドウが表示されます。実際、画面に表示されているかどうかにかかわらず、ほとんどの要素はウィンドウ内に存在します。最も一般的なアプリケーションのウィンドウなど、画面上で区切られた長方形の領域も 1 つのウィンドウ形式です。すべての種類のコントロールがウィンドウというわけではありませんが、リスト ボックス、スクロール バーなど、フォーム上にあるコントロールもウィンドウである場合があります。デスクトップに表示されるアイコン、およびデスクトップ自体もウィンドウです。

今回操作するダイアログも、もちろん「ウィンドウ」と「コントロール」で構成されています。

ではWindows上でこれら「ウィンドウ」と「コントロール」はどの様に管理されているでしょうか。

実は全ての項目には整数の”識別番号”が割り振られており、その番号によって管理されているのです。この識別番号は”ハンドル”と呼ばれます。

例えば先ほどのファイル選択ダイアログの場合ですと、

※番号は全て例です。また、ウィンドウやコントロールも大まかに色を付けているだけで、実際はもっと細かく分かれています。

この様に複数のウィンドウとコントロールで構成されており、それぞれハンドルの値は異なります。この図の場合、ファイル名の設定に関する操作を行いたい場合は、「78845678」というハンドルを使用すれば良いことになります。


スポンサーリンク

ダイアログのハンドルを取得する

ダイアログを操作するにはハンドルの値が必要ということが何となく分かったと思います。ではこれから実際にハンドルの値を取得してみましょう。

「え?わざわざ取得しなくてもさっき”78845678”って言ってたじゃない。これを使えばいいんでしょ?」

と思うかもしれませんが、ハンドルの値というのは固定で割り振られている訳ではありません。ウィンドウが作られた段階でハンドルが割り当てられますが、ウィンドウが閉じられるとそのハンドルは解放されます。再度ウィンドウが作成された場合はまた新たなハンドル値が割り振られますので、最初と同じハンドルが割り当てられるとは限らないのです。

ハンドルを取得するには「FindWindow」関数を使用します。

・クラス名は「#32770」を指定します。この値はダイアログのクラス名となり、基本的にこの名前は固定です。

・ウィンドウ名は「アップロードするファイルの選択」を指定します。

この部分がウィンドウ名です。

これらをあわせて以下の様に記述します。

※使用する場合はAPI宣言が必要になります。
※環境によってはFunctionの前に”Declare PtrSafe”等の記述が必要になります。

上記のコードを実行することで取得できた値が、そのファイル選択ダイアログのハンドルです。

コントロールのハンドルを取得する

ファイルを選択して決定させるためには、”ファイル名入力ボックス”と”開くボタン”を操作する必要があります。こちらのコントロールについてもハンドルを取得しましょう。

ハンドルを取得するには「FindWindowEx」関数を使用します。

・親ウィンドウのハンドルは先ほど取得したダイアログのハンドルを指定します。

・子ウィンドウのハンドルは無し。

・入力ボックスのクラス名は「Edit」。ウィンドウ名は無し。

・ボタンのクラス名は「Button」。ウィンドウ名は「開く(&O)」となります。

今回クラス名の調べ方については省きます。

それでは上記の内容をもとに以下の様に記述します。

実は入力ボックスについては幾つかのコントロールの入れ子になっているので、「ダイアログ」の中にある「ComboBoxEx32」の、更に中にある「ComboBox」の・・・といった様に順番に掘り下げていく必要があります。

先ほどのダイアログのハンドルを取得する処理と組み合わせると、”入力ボックス”、”ボタン”のハンドルもそれぞれ取得できることを確認できると思います。


スポンサーリンク

メッセージを送信する

ではこれで必要なデータ(ハンドル)は全て揃いました。

後はダイアログへ情報(メッセージ)を送信するだけですね。

情報の送信には「SendMessage」関数を使用します。

・送信先ウィンドウのハンドルは先ほど取得したハンドルを指定します。
 入力ボックスは”hInputBox”、ボタンは”hButton”の値を指定します。

・Msgには実行したいアクションを指定します。

・wParam、lParamは文字通りパラメータの指定です。送信したいファイルパスなどをこちらに指定します。

それでは上記の内容をもとに以下の様に記述します。

このコードをハンドルを取得する処理と組み合わせて実行すると、『Cドライブ直下にある「test.jpg」というファイルを選択して、開くボタンを押す』という処理が行われます。

実際にファイルを置いて動作を確認してみましょう。一つ一つの動作を確認するためにも、ステップインで一行ずつ処理させながら変数の中身を見ていくと分かりやすいと思います。

最後に

いかがでしたでしょうか。処理は思うように動きましたか?

一行ずつ処理させた場合はうまくいくけど、通しで実行するとうまくいかない」という場合は、処理の間にSleepを入れてみるなどして処理に余裕を持たせてみましょう。Sleepだけだと確実性に欠けるという場合は、DoLoopと組み合わせて「ハンドルの値が取れるまでループさせる」といった処理を加えるとより安定すると思います。

今回使用した”SendMessage”は、ダイアログ操作以外にも色々なことに使用することができます。今度はまた別の用途で紹介してみますね。

ではまた!


スポンサーリンク

シェアする

  • このエントリーをはてなブックマークに追加

フォローする

コメント

  1. しろ より:

    いろいろ調べてこちらへたどり着きました。
    すごく参考になり無事にツールが作れそうです。
    大変感謝しております。ありがとうございました!

    • 藤美れいん より:

      参考になった様で良かったです!
      他にも分からない処理などがありましたら遠慮無く質問して下さいね!
      内容次第では記事にして詳しく解説させて頂きます。

  2. くろねこ より:

    ‘ファイルパス入力’
    SendMessage(hInputBox, &HC, 0, “C:\test.jpg”)

    の部分が実行不可能で

    ‘ファイルパス入力’
    Call SendMessage(hInputBox, &HC, 0, “C:\test.jpg”)

    にしたら実行できました

  3. じょるじゅ より:

    この記事は大変わかりやすく、非常に参考になりました。感謝申し上げます。
    一点教えてください。”アップロードするファイルの選択”を、Microsoft IME 辞書ツール起動時の”テキスト ファイルからの登録”に置き換えて試行中ですが、うまくいきません。
    記述中の「ダイアログのハンドルを取得する」については、そのまま流用できると考えておりますが、「コントロールのハンドルを取得する」「メッセージを送信する」で変わってしまう値はございますでしょうか?

    • 藤美れいん より:

      ご質問頂いた件についてこちらで確認してみましたが、記事内の処理のままで「ファイルパスの入力」から「開くボタンの押下」まで問題無くできた事を確認致しました。

      どのあたりの処理までが出来て、どの部分の処理に失敗したかをお教え頂ければ何かアドバイスできるかもしれません。

  4. よう より:

    こちらの記事のお陰で業務用マクロが作れそうです!あまりこの手の記事が見当たらずほんとうに助かりました!すみませんが、もしお時間ございましたら2つほどご教授ください。

    1つめです。
    sendmessageの箇所で、ボタンをアクティブにするという処理が一文あるのですが、こちらは必要なのでしょうか?単にその下のボタンをクリックする処理さえあれば良さそうな気がしております(実際に試してみても問題がないような気が致します)。

    2つめです。
    記事中にも記載があるように、どうも待ち時間や繰り返し処理を入れていないせいか、5%くらいの確率でエラーになってしまいます。対応として、Sleepだと確実性に欠けるというのはまさにおっしゃるとおりですので、できればDoLoop(かIf)を使いたいです。ウィンドウ等の取得成否については、「Do Until hWindow 0」 というような感じで良いと思うのですが、「hInputboxに適切に値が代入できていたら(開くボタンをクリックする)」という条件はどういうふうに指定できますでしょうか?変なことを言うなと思われるかもしれませんが、タイムラグがあるせいか稀にフルパスの入力に失敗しているようです。

    すみません、もしよろしかったら教えてください!

    • 藤美れいん より:

      1>
      確かにアクティブを外しても動作しましたね(^^;
      アクティブにしないと正常に押せない場合もあったのですが、環境に依存するのかもしれません。
      問題無く動作するのであればアクティブ処理は外しても問題ありません。

      2>
      私の場合は、「開く」ボタンを押した後に『ダイアログが消えるまでDo~Loopで待機する』という処理を入れています。

      【一定時間経過してもダイアログが残っている場合】
      -パスのセットに失敗していると判断して処理の先頭に戻ってやりなおす
      【一定時間以内にダイアログが消えた場合】
      -ループを抜けて次の処理へ

      という感じですね。
      他には値をセットする際のSendMessageの第二引数に「&HC」を指定していますが、ここを「&HD」にすることで入力ボックスの内容を取得することもできます。
      それで見ても良いんですが、じゃあ値をセットしてからどの位待って取得すれば良いのかとか、判定を誤って2回送信してしまった場合はとかを考えるとちょっと面倒かなと。
      こんな感じでやり方は色々とありますので、お好きな方法で挑戦してみて下さい。

      • よう より:

        アドバイス大変ありがとうございました。アドバイスどおり、ダイアログが消えたかどうかをLoopUntilで使ってみて、正しく動作しております。

        取り急ぎ課題は解決したのですが、1つお伺いしたいことがございます。Sendmessage関数の引数について先程も教えていただいてありがたかったのですが、Sendmessage関数の解説のようなものはどういうところから情報収集しておられるのでしょうか?

        例えば、第四引数の「0&」の「&」とはなんぞや?など。ググってみても、「0」が使われていることが多いようなんですが、じゃあ使える引数として何どのようなののがあって、違いは何があって、というような解説資料がなかなかヒット致しませんでした。ご教授いただいた「&HC」も適切なものがヒットせず…。自習するにあたって参考にされているようなものがございましたら教えていただければ幸いです!

        • 藤美れいん より:

          うーん・・・私も何かの処理を実装したい時にその都度調べているだけですので、ご紹介できる様な資料やページは無いですね。
          お力になれずすみません。

  5. じょるじゅ より:

    早々のご回答に感謝申し上げます。お世話になります。
    ”テキスト ファイルからの登録”のダイアログを表示させた状態で実行すると、上手く登録できることが確認できます。ところがダイアログを表示させていない状態だと登録ができません。
    このプログラムはもしかして、予めダイアログを表示させていることを前提にしたものなのでしょうか?もしそうであれば、見当違いの質問であった旨、何卒ご容赦ください。

    • 藤美れいん より:

      そうですね。こちらに掲載している内容は、『ダイアログが既に開かれている状態』ということが前提になります。
      ですので、ダイアログを開くまでの処理は別途記述しなければいけません。

      IME辞書を変更するといった処理であれば、VBAで実装するよりもバッチでレジストリを操作したり、何らかのツールを用いた方が良いかと思います。

  6. じょるじゅ より:

    お世話になっております。お知恵をお貸し頂きありがとうございました。ダイアログを開くまでの処理は、VBSでsendkeyを使って処理いたしました。

    ご面倒をお掛けして申し訳ございませんが、別途質問させてください。
    現状はこのVBAプログラムで問題ないのですが、後々ファイル名が変わることを想定し、ワイルドカードでも起動できるようにしておきたいと考えております。”D:\jisho\*.txt”を事例として、どのように追記したらよいでしょうか?或いはワイルドカードは使えないのでしょうか?

    • 藤美れいん より:

      「ワイルドカードでも起動できるようにしたい」という内容は良く分かりませんが、単純にファイル名が変わる可能性があるという事であれば、その辞書ファイルが格納されているフォルダを予めプログラム上に持っておいて、ファイル名はプログラム実行時に読み取る様にすれば良いのでは無いでしょうか。
      何に対してワイルドカードを使いたいのでしょうか?

  7. じょるじゅ より:

    ご返答ありがとうございます。本当に助かります。

    現状はファイル名がa.txtなのですが、b.txtでも動くようにしたいと考え、ワイルドカード(*.txt)で構築したいと考えております。具体的には以下のように記述しましたが、a.txtを拾ってくれず、エラーとなってしまいます。

    Sub Test()
    Dim hInputBox As Long
    Dim hButton As Long
    Dim hWindow As Long
    Dim strPattern As String, strFile As String

    hWindow = FindWindow(“#32770”, “テキスト ファイルからの登録”)

    hInputBox = FindWindowEx(hWindow, 0&, “ComboBoxEx32”, “”)
    hInputBox = FindWindowEx(hInputBox, 0&, “ComboBox”, “”)
    hInputBox = FindWindowEx(hInputBox, 0&, “Edit”, “”)
    hButton = FindWindowEx(hWindow, 0&, “Button”, “開く(&O)”)

    strPattern = “D:\jisho\*.txt”
    strFile = Dir(strPattern)
    Call SendMessage(hInputBox, &HC, 0, strFile)
    Call SendMessage(hButton, &H6, 1, 0&)
    Call SendMessage(hButton, &HF5, 0, 0&)

    End Sub

    どこが悪いのかご教授頂けると幸いです。

    • 藤美れいん より:

      セットするパスのファイル名にワイルドカードは使用できません。
      「*.txt」とファイル名を指定しても、ダイアログ側はどのファイルを読み込めばいいのか分かりませんよ。

      ですので、「D:\jisho」というフォルダの中にあるファイル名を実行の度に読み込む様にして下さい。
      Dir関数で検索すれば方法は出てくると思います。

  8. じょるじゅ より:

    出来ました!ありがとうございました!

  9. waimar より:

    初めまして、この記事を読ませていただきました。大変わかりやすく書かれていて、全くの初心者である私もある程度のことまでは理解できそうです!
    一つ質問させていただきたいのですが、私の所でプログラムを動かしたところ、
    hButton = FindWindowEx(hWindow, 0&, “Button”, “開く(&O)”)
    のところのハンドル値が0になってしまったのですが、これちゃんとハンドル値が取得できていないということですよね?自分の画像データが、この記事にあるとおりに開けなくて困っています。何卒よろしくお願いします。

    • 藤美れいん より:

      最後のボタンのハンドルを取得する所で失敗しているのですね。
      確認ですが「hWindow」、「hInputBox」についてはそれぞれハンドル値が取得できているという事で宜しいでしょうか?
      また、「アップロードするファイルの選択」というウィンドウは表示されたままとなっていますか?

      hInputBoxの値が取得できているのであればhWindowの値も間違いという事ですので、考えられる原因としては「開く(&O)」の部分の指定が誤っている可能性位なのですが・・・

      • waimar より:

        返事が遅れてしまい申し訳ございません。あのあと試行錯誤の結果、上記のエラーは解決できました。そして、無事に自動化のプログラミングを組むことにも成功しました(自動出品のプログラムです)。この記事に巡り会わなかったら、ここまでたどり着けませんでした。本当にレインさんには感謝しています。ありがとうございました。

        • 藤美れいん より:

          無事解決した様で良かったです!

          しかし全くの初心者と仰っていましたが・・・この短期間でまさか自動出品プログラムまで完成させられるとは凄いですね!
          習得が速くてうらやましい限りです。(^^;