数千行を処理するマクロを、画面更新を止めて走らせると、Excelが固まったように見えます。終わるまで何も出ないので、動いているのか止まっているのか判断できません。
実務では、ラベルの文字だけを書き換える簡易な進捗表示を、非モーダルのUserFormで作っています。フォームには出すメッセージもサイズも位置も持たせず、枠だけ用意して、中身はすべてコードで指定する作りです。この記事では、その作り方と、使うときに押さえておく点を実測してまとめます。
先に結論
.Show vbModelessは0.016秒で戻り、フォームを出したままマクロが進みます- ラベルを書き換えただけでは、処理中の画面に文字が出ません。
RepaintかDoEventsを呼ぶと出ます Unloadすると指定は消えて、枠だけの状態に戻ります。開くたびにコードで指定し直します- 閉じ忘れると、マクロが終わってもフォームが画面に残ります
- 正常終了とエラーを同じ後片付けに流して、そこで
Unloadします
測った環境は、Excel 16.0(ビルド20527)、Windows 64bit、日本語版です。
フォームは名前だけ用意する
VBEで「挿入 → ユーザーフォーム」を選び、オブジェクト名だけ決めます。
- フォームのオブジェクト名を
frmProgressにする - ツールボックスからラベルを1つ置き、オブジェクト名を
lblMsgにする - ほかのプロパティ(Caption・サイズ・位置・文字)は触らない

この枠は、読み込んだ直後は幅240・高さ180、ラベルの文字は Label1 でした。Caption・サイズ・位置・メッセージは、開くときにコードで指定します。
Private Sub OpenProgress(ByVal msg As String)
With frmProgress
.Caption = "処理中"
.StartUpPosition = 0
.Width = 300
.Height = 90
.Left = Application.Left + (Application.Width - .Width) / 2
.Top = Application.Top + (Application.Height - .Height) / 2
With .lblMsg
.Left = 12: .Top = 12: .Width = 260: .Height = 18
.Caption = msg
End With
.Show vbModeless
End With
End Sub
Private Sub UpdateProgress(ByVal msg As String)
frmProgress.lblMsg.Caption = msg
frmProgress.Repaint
End Sub
Public Sub RunMain()
Dim eNo As Long, eSrc As String, eDesc As String
Dim i As Long
On Error GoTo Cleanup
OpenProgress "準備中"
For i = 1 To 5
UpdateProgress "処理中 " & i & " / 5"
'(ここに本処理)
Next i
Cleanup:
eNo = Err.Number: eSrc = Err.Source: eDesc = Err.Description
On Error Resume Next
Unload frmProgress
On Error GoTo 0
If eNo <> 0 Then Err.Raise eNo, eSrc, eDesc
End Sub
位置は、Excelのウィンドウの中央にしています。最大化したウィンドウ、小さくしたウィンドウ、別モニターに置いたウィンドウの3通りで、フォームはExcelウィンドウの中央に出ました。

.Show vbModeless は表示したまま次の行へ進む
引数なしの .Show はモーダルで、フォームを閉じるまで次の行へ進みません。実際に、×で閉じるまで戻らず、今回は8.1秒かかりました。vbModeless を渡すと、0.016秒で戻ります。続きのコードが実行されることは、Microsoft Learnの Show method にも書かれています。
ラベルを書き換えても画面に出ない
5段階の処理を、各段階で1秒待つだけの処理にして、フォームに見えた表示を記録しました。
| やり方 | 見えた表示 |
|---|---|
| ラベルを書き換えるだけ | 文字なし |
書き換え+Repaint | 1〜5の全部 |
書き換え+DoEvents | 1〜5の全部 |
段階ごとに Unload、指定し直して再表示 | 文字なし |
ScreenUpdating = False+書き換えるだけ | 文字なし |
ScreenUpdating = False+書き換え+Repaint | 1〜5の全部 |
書き換えるだけでは、フォームの枠は出ても文字が出ません。段階ごとに閉じて開き直しても、待つだけの処理では出ませんでした。ScreenUpdating = False でも、Repaint を呼べば文字は出ます。Microsoft Learnの Repaint method は、再描画を待たずに表示を更新するメソッドです。
更新は毎回やらない
ループ2万回で、更新の方法ごとに3回ずつ測りました。
| パターン | 平均(秒) | 1回あたり |
|---|---|---|
| 更新なし | 0.003 | – |
| 書き換えるだけ(画面には出ない) | 0.091 | 0.004ミリ秒 |
毎回 Repaint | 5.081 | 0.25ミリ秒 |
毎回 DoEvents | 7.164 | 0.36ミリ秒 |
100回に1回 Repaint(200回) | 0.057 | – |
100回に1回 DoEvents(200回) | 0.117 | – |
| ステータスバーを毎回 | 5.641 | 0.28ミリ秒 |
毎回更新すると、2万回で5秒前後かかります。100回に1回にすると、0.06秒で済みました。ステータスバーも、1回あたりの時間は同じくらいです。Timer の刻みが粗いので、0.1秒未満の値は目安です。
シート操作が重くなる仕組みは、【VBA】処理が遅い本当の原因|シート操作が重くなる仕組み で測っています。
Unloadを忘れると何が残るか
Unload を忘れて正常終了すると、フォームは画面に残ります。UserForms.Count は1、Visible は True、ラベルの文字は最後に書いたメッセージのままでした。右上の×で閉じると、Count は0になります。
Hide は見えなくするだけです。Hide → Show では、文字もサイズも指定したままで、Unload → 再表示では、最初の枠(Label1、幅240・高さ180)に戻ります。
Unload したあとに frmProgress.lblMsg.Caption を読み書きすると、フォームが見えないまま読み込み直されます(Count が1)。Set f = New frmProgress で変数に持つ場合は、Unload f のあとで中身を読んでも Count は0のままでした。
エラーでも正常終了でも必ずUnloadする
エラー処理を書いていないと、エラーは呼び出し元に届き、Unload まで進まないのでフォームが残ります(ゼロ除算のエラーで、UserForms.Count は1でした)。
上のコードのように Cleanup に流す書き方では、エラーが途中でも、Show の前でも、正常終了でも、Count は0です。先に Err の内容を退避して、Unload のあとで Err.Raise するので、エラー番号とメッセージもそのまま呼び出し元に届きます。Exit Sub を Cleanup の手前に置くと、正常終了のときに Unload が走らず、Count は1になります。
ScreenUpdating などの設定を元に戻す後片付けも、同じ形で書けます。詳しくは 【VBA】エラー時も設定を元に戻す|ScreenUpdating等の後片付け にまとめています。
止めたときもフォームは残らない
エラーのダイアログで「終了」を押したとき、「デバッグ」からVBEのリセットを押したとき、どちらもフォームは残りませんでした(UserForms.Count は0)。エラーのダイアログが出ている間は、フォームは画面に見えています。
Escキーでの中断は、今回の待つだけの処理では効きませんでした。マクロを終了させたあとは、フォームは残っていません。
まとめ
.Show vbModelessで、フォームを出したままマクロを進められます- 文字を出すには、書き換えたあとに
Repaint(またはDoEvents)を呼びます。毎回ではなく、一定の件数ごとに呼べば、時間はほとんど増えません Unloadで指定は消えるので、開くたびにコードで指定し直します- 後片付けは
Cleanupに集めて、正常終了でもエラーでもUnloadします


コメント