「お待ちください」メッセージの表示


マクロで時間がかかる処理を行うとき、画面に「お待ちください」や「処理中です」などのメッセージを表示するにはいくつかの方法があります。ここでは、次の3つの方法をご紹介します。

ワークシートを使った疑似メッセージ

最も簡単な方法のひとつは、「お待ちください」や「処理中です」などのメッセージをワークシート上にあらかじめ作成しておき、時間のかかる処理中にそのワークシートを表示することです。まずSheet3に次のようなメッセージを作成します。

そして、処理に時間のかかるマクロでは、次のようにSheet3を表示します。なお、本稿では「時間かかるマクロ」としてC:\Windows\System32フォルダ内の各ファイルサイズを合計する処理を行います。実行するパソコンによっては、処理時間に差が出ますのでご了承ください。また、本当はもっと高速に処理することも可能ですが、今回はあえて時間のかかる方法を採用しています。その部分へのツッコミはおやめください(^^;

Sub Sample1()
    Dim TotalSize As Long, buf As String
    Worksheets("Sheet3").Activate
    ''ここから時間のかかる処理
    buf = Dir("C:\Windows\System32\*.*")
    Do While buf <> ""
        TotalSize = TotalSize + FileLen("C:\Windows\System32\" & buf)
        buf = Dir()
    Loop
    Worksheets("Sheet1").Activate
End Sub

この方法は手軽ですが、いくつかの制約もあります。たとえば、表示するワークシートを変更してしまうので、アクティブシートのセルを操作するようなマクロでは工夫が必要です。また「お待ちください」と表示しただけでは、マクロが終了するまで非常に長い時間がかかるときなど、ユーザーが「ハングアップしたのか?」と不安になります。この簡易テクニックに限りませんが、「お待ちください」メッセージを表示するときは、何らかの形で現在の状況をユーザーに知らせてあげるべきです。では、その2つを加えたサンプルを次に示します。

Sub Sample2()
    Dim TotalSize As Long, buf As String, ReturnSheet As Worksheet
    Set ReturnSheet = ActiveSheet           ''(1)
    Worksheets("Sheet3").Activate           ''(2)
    Application.ScreenUpdating = False      ''(3)
    ReturnSheet.Activate                    ''(4)
    ''ここから時間のかかる処理
    buf = Dir("C:\Windows\System32\*.*")
    Do While buf <> ""
        TotalSize = TotalSize + FileLen("C:\Windows\System32\" & buf)
        Application.StatusBar = buf & "を調査中..."
        ActiveCell = TotalSize
        buf = Dir()
    Loop
    Application.ScreenUpdating = True       ''(5)
    Application.StatusBar = False
End Sub

流れは次のようになります。ここでは、操作したいシートがSheet1で、「お待ちください」メッセージをSheet3に作ったとします。

  1. 現在のシート(操作したいシート)を変数ReturnSheetに記憶します
  2. 「お待ちください」メッセージのSheet3を開きます
  3. ScreenUpdatingプロパティで画面の更新を抑止します
  4. 変数ReturnSheetに記憶しているシート(Sheet1)を開きます
  5. 処理が終わったらScreenUpdatingプロパティで画面更新の抑止を解除します

現在の状況はステータスバー(Application.StatusBar)に表示します。ScreenUpdatingプロパティで画面の更新を抑止していても、ステータスバーは通常通り使えます。処理が終わったら、Application.StatusBarにFalseを指定して、ステータスバーの表示を戻してくださいね。

UserFormを使ったメッセージ(Excel 97まで)

次に、Windowsのアプリケーションなどでよく見かける[お待ちください]ダイアログボックスを表示する方法を解説します。このテクニックで最大のポイントは「時間のかかる処理をUserFormに実行させる」という考え方です。たとえば次のようなコードではどうでしょう。

Sub Sample3()
    Dim TotalSize As Long, buf As String
    MsgBox "ただいま処理中です..." & vbCrLf & "お待ちください。"
    ''ここから時間のかかる処理
    buf = Dir("C:\Windows\System32\*.*")
    Do While buf <> ""
        TotalSize = TotalSize + FileLen("C:\Windows\System32\" & buf)
        buf = Dir()
    Loop
End Sub

時間のかかる処理を行う前にMsgBoxでメッセージを表示してみました。結果は失敗です。メッセージは表示されるのですが、そのメッセージが表示されている間はマクロが停止してしまいます。「''ここから時間のかかる処理」以降を実行するには、メッセージの[OK]ボタンをクリックして閉じなければなりません。失敗の原因は次のような流れだからです。

「お待ちください」メッセージを表示するには、そのメッセージが表示されているバックグラウンドで、時間のかかる処理が行われるような仕組みを作らなければなりません。それにはUserFormを使います。

ここからは、少し複雑になりますから詳細に解説しますね。まずUserFormを1つ挿入してください。名前は「UserForm1」のままでけっこうです。時間のかかる処理は、標準モジュールのSub Sample4から行うものとします。さて、挿入したUserFom1を次のようにデザインします。

ラベルを2つ配置して、それぞれにメッセージを書きました。ここは動作に関係ありませんから、お好きなようにデザインしてください。デザインが完了したら、UserFormをダブルクリックして次のプロシージャを開きます。

VBE右上にある[プロシージャ]リストボックスの▼をクリックして「Activate」を選択します。

実行すると次のように「Private Sub UserForm_Activate」が挿入されます。今回使うのはこのプロシージャです。

挿入された「Private Sub UserForm_Activate」に、次のようなコードを書きます。時間のかかる処理を「Private Sub UserForm_Activate」から実行させるのです。

Private Sub UserForm_Activate()
    Dim TotalSize As Long, buf As String
    Me.Repaint
    ''ここから時間のかかる処理
    buf = Dir("C:\Windows\System32\*.*")
    Do While buf <> ""
        TotalSize = TotalSize + FileLen("C:\Windows\System32\" & buf)
        buf = Dir()
    Loop
    Unload Me
End Sub

プロシージャの先頭に追加した「Me.Repaint」は、UserFormを強制表示させる効果があります。まぁ、念のために書くおまじないだと思ってください。そして最後には「Unload Me」で自分自身を閉じるようにします。時間のかかる処理が終わったらメッセージも自動的に閉じないとマヌケですからね。

そして、このUserFormを表示するために、標準モジュールの「Sub Sample4」に次のようなコードを書きます。

Sub Sample4()
    UserForm1.Show
End Sub

Sample4はUserForm1を呼び出すだけでいいのです。ここで書いたとおりに試したところ正常に動作しました。うまくいかない人は、どこかで間違えていないかチェックしてください。

UserFormを使った「お待ちください」メッセージでは、時間のかかる処理をUserFormに実行させるという考え方が重要です。つまり、次のような流れです。

また、ここでも「現在の状況」をユーザーに知らせる工夫が必要ですが、それはまた別のトピックで詳しく解説します。

UserFormを使ったメッセージ(Excel 2000以降)

お使いのExcelがExcel 2000以降のバージョンでしたら、もう少し簡単な手もあります。それは、UserFormをモードレスで表示する方法です。一般的なUserFormでは、UserFormを表示している間はワークシートやセルを操作できません。そのようなUserFormを「モーダル」な状態と呼びます。それに対して、UserFormを表示している間でもワークシートやセルを操作できる状態を「モードレス」と呼びます。Excel 2000からは、UserFormを表示するShowメソッドに引数を指定できるようになり、UserFormをモードレスで表示することが可能になりました。モードレスなUserFormは、UserFormを画面に表示した直後に、Showメソッドを実行した呼出元のプロシージャに制御が戻りますので、「お待ちください」などのメッセージ(UserForm)を表示したままで次の処理を行うことができます。

Sub Sample5()
    Dim TotalSize As Long, buf As String
    UserForm1.Show vbModeless
    UserForm1.Repaint
    ''ここから時間のかかる処理
    buf = Dir("C:\Windows\System32\*.*")
    Do While buf <> ""
        TotalSize = TotalSize + FileLen("C:\Windows\System32\" & buf)
        buf = Dir()
    Loop
    Unload UserForm1
End Sub

UserFormを呼び出すShowメソッドの引数に定数vbModelessを指定すると、そのUserFormはモードレスになります。定数vbModelessの実体は0です。UserFormのActivateイベントなどでは、自分自身(UserForm)をMeというキーワードで参照できましたが、上のように標準モジュールなどからUserFormを操作するときには、もちろんMeキーワードを使用できません。RepaintメソッドやUnloadメソッドで、MeではなくUserForm1などと名前を指定している点に留意してください。

処理を停止できるメッセージ

さて最後に、「お待ちください」メッセージに[終了]ボタンを用意して、処理の途中でユーザーが中止できるような工夫を解説します。上の「UserFormを使ったメッセージ(Excel 97まで)」で作ったUserFormを例に解説しましょう。

UserFormを次のようにデザインします。

コマンドボタン(CommandButton1)を追加して、Captionを「終了」に変えました。この[終了]ボタンをダブルクリックしてコードペインを開きます。

マクロの実行中に、ユーザーが[終了]ボタンをクリックすることで処理を中止できるようにする仕組みは、次のようになります。

まず、「Private Sub CommandButton1_Click」と「Private Sub UserForm_Activate」の両方で使用できる変数flagを用意します。「Private Sub CommandButton1_Click」でユーザーが中止を選択したら、この変数flagにTrueをセットします。「時間のかかる処理」の中では変数flagをチェックして、もし変数flagがTrueだったら処理を中止します。

Dim flag As Boolean
Private Sub CommandButton1_Click()
    If MsgBox("中止しますか?", 292) = vbYes Then flag = True
End Sub
Private Sub UserForm_Activate()
    Dim TotalSize As Long, buf As String
    Me.Repaint
    ''ここから時間のかかる処理
    buf = Dir("C:\Windows\System32\*.*")
    Do While buf <> ""
        DoEvents
        If flag = True Then Exit Do
        TotalSize = TotalSize + FileLen("C:\Windows\System32\" & buf)
        buf = Dir()
    Loop
    Unload Me
End Sub

ポイントの1つめはDoEvents関数です。時間のかかる処理を行っていると、CPUがその処理に没頭してしまい、その他の操作ができなくなる場合もあります。そうすると、せっかく用意した[終了]ボタンもクリックすることができなくなってしまいます。それを防ぐために、DoEvents関数を実行してCPUの処理を解放してやります。理屈が理解できなくても、必須のおまじないだと思ってください。

次のポイントは、もし変数flagがTrueだったときの処理です。時間のかかる処理はDo Loopステートメントの内部で行われていますので、中止するときはこのループ(繰り返し)を強制終了させなければなりません。Exit Doがその働きをします。Exit Doに関しては「ループ(繰り返し処理)からの強制脱出」で解説していますのでご覧ください。

Excel 2000以降で「モードレス」なUserFormを使う場合も考え方は同じです。このときは、UserFormの呼出元(標準モジュールなど)と、UserForm内のプロシージャ(CommandButton1_Clickなど)の両方で参照できる広域変数が必要ですので、変数flagは標準モジュール側で宣言します。

【標準モジュール】

Public flag As Boolean
Sub Sample5()
    Dim TotalSize As Long, buf As String
    UserForm1.Show vbModeless
    UserForm1.Repaint
    flag = False
    ''ここから時間のかかる処理
    buf = Dir("C:\Windows\System32\*.*")
    Do While buf <> ""
        DoEvents
        If flag = True Then Exit Do
        TotalSize = TotalSize + FileLen("C:\Windows\System32\" & buf)
        buf = Dir()
    Loop
    Unload UserForm1
End Sub

【UserForm1】

Private Sub CommandButton1_Click()
    If MsgBox("中止しますか?", 292) = vbYes Then flag = True
End Sub

UserForm側で宣言した変数flagは、UserFormがLoadされるたびに初期化されますので、初期値は毎回Falseになります。しかし、標準モジュール側で宣言した広域変数は、プロシージャが終了してもクリアされない場合がありますので、時間のかかる処理の前で明示的にFalseを指定しておくといいでしょう。ちなみに、こうしたケースで広域変数の値が保持されるかどうかについて誤解している方がとても多いです。非常に奥の深い話ですが、機会があったら別のトピックで詳しく解説しましょう。

冒頭にも書きましたが、時間のかかる処理を行うときはユーザーに「現在処理を行っている」といったアナウンスをするべきです。本稿ではそのために「お待ちください」メッセージを表示するテクニックを解説しましたが、本来なら「現在何を処理している」や「現在どこまで処理が終わった」「完了するまでにはどれくらいかかる」などの情報も提示するべきです。とにかくユーザーに"不安"や"誤解"を与えてはいけません。そうした「進行状況の表示」に関しては、また別のトピックで解説します。