If MsgBox("OK か Cancel を押すまで、呼び出し元の画面を操作できません。", vbOkCancel) = vbCancel Then
Exit Sub
End If
上記の記述だと、メッセージボックスを閉じるまで呼び出し元の画面を操作できません。
そこで、呼び出し元を操作可能なメッセージボックスを自作してみます。
ここでは UserForm
をメッセージボックス代わりに使います。
しかしそのまま Show メソッドで呼び出すと、下記のような問題が生じます。
- モーダル(
vbModal
)指定だと、呼び出し元を操作できないままになってしまう - モードレス(
vbModeless
)指定にすると、呼び出し元で待ち合わせることなく、すぐに次の行が実行されてしまう。
これを解決する実装を組んでみました。呼び出す側はこのように書きます。
If Module1.ShowUserForm1Modally("閉じるまで待ち合わせつつ、呼び出し元の操作も可能") = vbCancel Then
Exit Sub
End If
モードレス画面を「疑似モーダル画面」として実装するにあたって、重要なのは 2 点です。
- 呼び出す側:モードレスな子画面が閉じられるまで、次のステートメントに進んでしまわないよう待ち合わせるための仕組み。
- 呼ばれる側:自画面が閉じられたとき、あるいは OK/Cancel ボタンが押されたときなどに、そのことを呼び出し側に伝えるための仕組み。
モードレス画面が閉じられるまで、呼び出し側はループ処理を使って待ち続けます。 無限ループするだけでは画面操作ができなくなるので、適宜 DoEvents 関数を呼び出す必要があります。
このとき、無条件に DoEvents
を呼び続けることもできますが、不必要なタイミングでも呼び出されてしまうため、MsgWaitForMultipleObjects API を併用して無駄な CPU 消費を抑えるようにします。
これによって実行中のスレッドが Windows Message (画面の再描画やマウス・キーボード操作など)を受信した時にだけ DoEvents
が呼び出されるようにすることができます。
今回は「OK/Cancel が押されたかどうか」「画面が閉じられたかどうか」を表すための変数を UserForm
上に設けています。
変数の値が変化したことを呼び出し元のモジュールに伝えるためには、「イベント通知を使う」「コールバックメソッドを呼び出す」といった手法がありますが、今回は UserForm
のモジュール上にて自分が自分自身を呼び出す形にすることで通知の手間を省いています。
ご紹介いただいた疑似モーダルダイアログをエクセルのVBAに組み込みました。
このダイアログの表示中に、エクセルのワークシートの操作ができます。
また、新たなブックを開くこともできます。
ところが、新たに開いたブックを閉じると、疑似モーダルダイアログも消えてなくなってしまって、どうしようもなくなります。
新たに開いたブックを閉じても、疑似モーダルダイアログが消えない様にすることはできますか?