質問: よろしくお願いいたします。 Excelでの質問です。 シート上にコマンドボタンを配置して、それをクリックすることで、様々な計算等の一連の処理を行なうように作りました。 そこまで来て、その処理に時間がかかるのが気になったため、「処理中です。手をふれないでそのまま待っててください」と表示させるためのフォーム(F_wait)を作り、処理開始から処理終了まで表示させることとしました。 処理の開始時に F_wait.show とだけ書くと、その時点で処理が中止されてしまうことがわかり、自分なりに調べたら ``` F_wait.show vbmodeless ``` と書けば、処理が中断せずに進行すると知り、そのようにして上手くいきました。 次に、同ファイルの別の場所での機能なのですが、あるモーダルフォームF1を表示させていて、そのF1上にあるコマンドボタンにも、 F1上のテキストボックスF1.txt1に入力されている値を使って、計算したり印刷したりシートのコピーを別ファイルとして保存したりといった、 一連の処理を作成しました。 この処理も処理に時間がかかるため、F_waitを表示させようとしたのですが、今度は 「モーダルフォームが表示されているときは、モードレスフォームを表示できません」とエラーが出て来てしまいます。 仕方がないからと、処理スタート時に、一旦 ``` unload F1 F_wait.show modeless ``` とするわけにはいきません。 実行したい処理において、F1上のテキストボックスの値を参照したいからです。 とはいえ、F1の上にF_waitをモーダルでshowすると処理が止まってしまいます。 パッと思いつく対策は、F1上のテキストボックス群の諸値を変数に持ち、F1がunloadされた後でもその変数を用いて処理を続行する、ということなのですが、 しかし、この方法は、あまりスマートじゃない感じがするのと、 見た目的に、大きなF1がクリック後も表示されたままその中央あたりに小ぶりなF_waitが重なるように表示されることで、使用者にとってちゃんと何らかの処理がされているんだなという印象を持たせることができそうなので、そうしたいです。 デモデモダッテになりますが、だからといって、F_waitは、他の処理や今後の機能拡張でも使い回したいので、F_wait自体に処理のコードを書くもの避けたいです。 こういう状態でグダグダとしているのですが、何かいい方法はないでしょうか? 「お待ちください」表示フォームのことをまったく考えないでほとんどの処理を書いてしまったのがまずかったとは思ってるのですが、どうしたもんでしょう? 経験豊富な方はどうしておられるのか教えてください。
回答: モーダルのフォームからはモーダルしか呼び出せないというのは、処理に一貫性を持たせるための仕様なのでそれは尊重すべきだと思います。 モーダルで実行しているときは、他の処理は実行できない、ユーザーも処理が終わるまでは何もできないという仕様ということなので。 また、F_wait を他に処理にも使いまわしがきくようにしたいということなので、 F_Wait から他の処理(プロシージャ)を呼び出せるように設計すればどうでしょうか。 標準モジュールに記述したプロシージャなら、Application.Run ユーザーフォームやシートモジュールに記述したプロシージャなら CallByName で呼び出せます。 **F_waitのモジュール** ``` Option Explicit Public ProcName As String Public obj As Object Public msg As String Private Sub UserForm_Activate() Me.Label1.Caption = msg If Not obj Is Nothing Then CallByName obj, ProcName, VbMethod Unload Me ElseIf ProcName <> "" Then Application.Run ProcName Unload Me End If End Sub ``` **F1 のモジュール** ``` Option Explicit Public Sub FormProc() MsgBox "FormProc" End Sub Private Sub CommandButton1_Click() Load F_Wait With F_Wait .ProcName = "FormProc" Set .obj = Me .msg = "FormProc を実行中です。しばらくお待ちください。" .Show End With End Sub Private Sub CommandButton2_Click() Load F_Wait With F_Wait .ProcName = "ModuleProc" .msg = "ModuleProc を実行中です。しばらくお待ちください。" .Show End With End Sub ``` **標準モジュール** ``` Option Explicit Public Sub ModuleProc() MsgBox "ModuleProc" End Sub ``` 改良案 上記のコードは、F_Wait の呼び出しがちょっと面倒です。呼び出しをメソッド(Subプロシージャ)にするとシンプルに呼び出せます。 **F_waitのモジュール** ``` Option Explicit Private sProcName As String Private sobj As Object Public Sub Start(ProcName As String, Optional msg As String, Optional obj As Object) sProcName = ProcName If Not obj Is Nothing Then Set sobj = obj Me.Label1.Caption = msg Me.Show vbModal End Sub Private Sub UserForm_Activate() If Not sobj Is Nothing Then CallByName sobj, sProcName, VbMethod ElseIf sProcName <> "" Then Application.Run sProcName End If Unload Me End Sub ``` **F1 のモジュール** ``` Private Sub CommandButton1_Click() F_Wait.Start "FormProc", _ "FormProc を実行中です。しばらくお待ちください。", _ Me End Sub Private Sub CommandButton2_Click() F_Wait.Start "ModuleProc", _ "ModuleProc を実行中です。しばらくお待ちください。" End Sub ``` F_Wait.Start とメソッドを呼び出すことで、F_Wait は自動でLoadされます。あとは、引数で、プロシージャ名等を渡せばいいだけです。