【VBA×WindowsAPI】メッセージボックスのボタンの文字列を変更する

VBAでメッセージボックスの「OK」や「キャンセル」ボタンの文字列を変更したいと思ったことはあるでしょうか。UserFormを使えばボタンの文字列や位置を調整できるため、ほとんどの場合はそんな状況に出くわさないと思いますが、一応WindowsAPIを使うことでメッセージボックスの文字列を変更することができます。かなり専門的で高度な処理を行いますが、コピペで再現自体は可能です。

ちなみに「キャンセル」を「Cancel」、「はい」を「Yes」のように日本語OSで英語OSの再現をしたいという場合は、本ページの内容でも実現することはできますがMessageBoxEx関数でロケールIDを指示する手法のほうが簡単かつ処理が単純なのでそちらをおすすめします。

メッセージボックスのボタンの文字列を変更

WindowsAPIを使うことで、上画像のようにMsgBox関数で表示されるメッセージボックスの文字列を変更することでができます。内部的にはあくまでも用意されているボタンはそのままで、描画されているテキストを書き換えているだけの処理となっています。(※戻り値のvbOKやvbCancelもそのまま)

メッセージボックスはモーダルウィンドウであるため、MsgBox関数を呼び出した後ではFindWindow関数などでアクセスすることができず、処理の対象であるウィンドウのハンドルを取得することができません。この問題を解決するためには、特定の処理の途中に別の処理を割り込ませる「フック」と呼ばれる技術を使います。このフック処理を使うことにより、メッセージボックスウィンドウが表示されるよりも前に「ボタンの文字列を変更させる」という処理を割り込ませることができるようになります。

VBAでメッセージボックスのボタンの文字列を変更するには下記のWindows APIを利用します。
それぞれ関数のより詳細な使い方の解説は各関数のリンクページを参照下さい。

icon-check-square SetWindowsHookEx関数         :フックプロシージャを起動してイベントを監視する
icon-check-square UnhookWindowsHookEx関数  :フックプロシージャを削除する (フックを解除する)
icon-check-square CallNextHookEx関数             :フック処理の途中で次のフックにメッセージを渡す 
icon-check-square GetCurrentThreadId関数        :実行中のスレッドIDを取得する
icon-check-square GetClassName関数         :ウィンドウのクラス名を取得する
icon-check-square SetDlgItemText関数               :指定コントロールの文字列を変更する

「そもそもWindows APIって何?」という方はコチラ(メインページ)も併せて参照下さい。

サンプルコード

メッセージボックスのボタンの文字列を変更するためのサンプルコードは下記の通りです。main関数を実行することで、ボタンの表記を変えたメッセージボックスを表示することができます。

CBTProcコールバック関数は本来、コンパイルなどされて処理が中断されることはない部分の処理であるため、ブレークポイントを置いたり、デバッグエラーなどで処理が止まるとアプリケーション(Excel等)が強制終了されます。CBTProcコールバック関数内にある変数の中身が知りたい場合は「Debug.Print」などを使って処理を止めないようにして確認する必要があるので注意が必要です。
 

コード解説

icon-edit CBTProcコールバック関数の定義

CBTProcコールバック関数は、ウィンドウ操作やユーザーインターフェースの制御を監視/フックするための関数で、特定のウィンドウイベント(ウィンドウの作成やサイズ変更など)に応じて指定の処理を行わせることができます。関数名に決まりはありませんが引数は下記の通りの設定が必要です。

ウィンドウイベントは引数のnCodeから受け取ることのできるコードによって判別することができます。ウィンドウがアクティブ化されたときは定数「HCBT_ACTIVATE」、ウィンドウが閉じられるときは定数「HCBT_DESTROYWND 」というように、CBTProcコールバック関数はウィンドウの状況に応じて決められたコードを常に受け取ることができるため、条件に応じて発火するイベントを追加することができます。(コードの種類はMicrosoft公式ページ参照)

ウィンドウイベントが発火した際には引数wParamから対象のウィンドウのハンドルを取得することができます。これにより、ウィンドウが画面に表示されるよりも前に、このウィンドウに対して何らかの処理(サンプルコードではボタン文字列の変更)を行うことができるようになります。

フック処理は終了しないと、CBTProcコールバック関数が常に呼び出され続けてしまいます。UnhookWindowsHookEx関数を使うことでフックの解除ができるため、基本的に実現したい処理が終わったタイミングで速やかに解除することが鉄則です。(アプリ再起動でもフックは解除される)
 

icon-edit CBTProcコールバック関数の設定

CBTProcコールバック関数の定義ができたら、SetWindowsHookEx関数を使ってフック処理として登録します。フック処理にもいくつかの種類(キーボードやマウスの入力監視等)がありますが、ウィンドウの状態のイベントとしてはCBTを利用します。(その他の種類はMicrosoft公式ページ参照)

icon-code  SetWindowsHookEx関数 

hHook = SetWindowsHookEx(WH_CBT, AddressOf CBTProc, 0&, ThreadId)

SetWindowsHookEx関数でCBTのフック処理を行うには上記のように記載します。ThreadIdには監視する対象のスレッドIDを入力します。VBAに紐づくウィンドウをフックしたい場合は基本的にGetCurrentThreadId関数で取得される値を入力すれば問題ないです。それ以外の引数は上記のような決まった値となります。これにより戻り値としてフックプロシージャへのハンドルを取得することができます。フック解除時はこのハンドルを使用するので保持しておく必要があります。

  icon-info-circle フックの監視範囲   

フック処理の登録時のスレッドIDにより監視する範囲の指定を行うことができます。このフックの監視範囲は可能な限り小さくする必要があります。というのも、フック範囲をすべて(0&)にしてしまうと、VBAとは関係ないところで動いている処理で表示されたウィンドウもフックの対象となってしまいます。無駄な処理が走ってしまうことはもちろんですが、意図しない処理が勝手に行われてしまう可能性があるので注意が必要です。

 

icon-edit ウィンドウのフック

上記の設定で基本的なフック処理の実装は完了です。これによりSetWindowsHookEx関数が実行された後、UnhookWindowsHookEx関数が実行されるまではCBTProcコールバック関数が呼び出され続けます。つまり、SetWindowsHookEx関数の実行後に新たなウィンドウであるメッセージボックスを表示すれば、メッセージボックス表示時の処理をフックすることができます。これはメッセージボックスに限らずUserFormやファイル選択ダイアログなどのウィンドウでも同じです。

本来はウィンドウが表示されてからFindWindow関数などでウィンドウハンドルを取得し、その後にウィンドウのボタンの文字列を変更する必要があります。しかしこの場合、1度ウィンドウが画面上に表示されてしまっているため、ボタンの文字列が切り替わる瞬間が見えてしまいます。フック処理を使うことで、ウィンドウが画面上に表示する直前にボタンの文字列を切り替える処理を割り込ませることができるため(いわゆる裏側で)この問題を解決することができるというわけです。

 

関連情報

icon-share-square VBA×WindowsAPIまとめページ

その他のWindowsAPI関数は下記ページにまとまっているので合わせて参照下さい。

icon-share-square 参考

Microsoft公式:フックの概要 – Win32 apps

Excel, VBA, Windows API

Posted by Lic