VBAのマクロは一度実行すると途中で止められませんが、ユーザーフォームのキャンセルボタン+フラグ変数+DoEventsを組み合わせることで、処理中でもユーザーが中断できる仕組みを作れます。大量データの処理中に「間違えた」と気づいたときのための安全策として、ぜひ取り入れておきましょう。
この記事では、次の内容を順番に解説します。
- キャンセル機能の仕組みとしくみ全体の流れ
- ユーザーフォームとキャンセルボタンの作り方
- フラグ変数とDoEventsを使ったメイン処理のコード
- フォームを使わずMsgBoxだけで実現する簡易版
- キャンセル後のロールバック(元に戻す処理)の考え方
キャンセル機能の仕組みを理解するには?
VBAのループ処理中にキャンセルを実現するには、次の3つの要素を組み合わせます。
| 要素 | 役割 |
|---|---|
| ユーザーフォーム(キャンセルボタン) | ユーザーが「やめる」と操作できる画面を用意する |
| フラグ変数(Public CancelFlg) | ボタンが押されたことをメイン処理に伝える |
| DoEvents | ループ中でもボタン操作などの外部イベントを受け付けるようにする |
処理の流れは次のとおりです。
- 処理開始と同時にキャンセルボタンつきのフォームを非モーダル表示する
- ループの各周回で
DoEventsを呼び、ボタン操作を受け付ける - ユーザーがキャンセルボタンを押すと
CancelFlg = Trueになる - 次のループで
CancelFlgがTrueになっていたらExit Forで処理を抜ける
ユーザーフォームとキャンセルボタンを作るには?
まずVBEでユーザーフォームを作成し、キャンセルボタンを配置します。
- VBEのメニューから「挿入」→「ユーザーフォーム」を選ぶ
- プロパティウィンドウで名前を
frmCancelに変更する - ツールボックスからコマンドボタンをフォームに配置する
- ボタンの名前を
cmdCancel、キャプションを「キャンセル」に変更する
次に、標準モジュールにフラグ変数を宣言します。
'標準モジュールの先頭に記述する
Public CancelFlg As Boolean
フォームのキャンセルボタンに次のコードを記述します(frmCancel のコードウィンドウを開いて入力)。
Private Sub cmdCancel_Click()
CancelFlg = True 'フラグをTrueにしてメイン処理に伝える
Unload Me 'フォームを閉じる
End Sub
メイン処理にキャンセルチェックを組み込むには?
メイン処理のループ内に DoEvents と CancelFlg のチェックを入れます。vbModeless でフォームを非モーダル表示にするのがポイントで、これにより処理を続けながらフォームのボタン操作ができるようになります。
Sub RunWithCancel()
Dim i As Long
CancelFlg = False 'フラグを初期化
frmCancel.Show vbModeless '処理と並行してフォームを表示
For i = 2 To 10000
'処理の本体
Cells(i, 2).Value = Cells(i, 1).Value * 2
DoEvents 'ボタン操作などのイベントを受け付ける
'キャンセルされていたらループを抜ける
If CancelFlg = True Then
MsgBox "処理をキャンセルしました。(" & i - 1 & "件処理済み)", vbExclamation
Exit For
End If
Next i
'処理完了またはキャンセル後にフォームを閉じる
If Not CancelFlg Then
MsgBox "処理が完了しました。", vbInformation
End If
Unload frmCancel
End Sub
DoEvents を入れる位置:処理が重いループでは、DoEvents を入れる間隔が長すぎるとボタン操作が反応しにくくなります。データ量が多い場合は、100件ごとなど一定間隔で呼ぶ書き方も有効です。
'処理が重い場合は一定間隔でDoEventsを呼ぶ
If i Mod 100 = 0 Then DoEvents
フォームを使わず簡易版のキャンセルを実現するには?
ユーザーフォームを作るのが手間な場合、MsgBox で確認ダイアログを定期的に出す方法が簡単です。完全なリアルタイムキャンセルではありませんが、一定件数ごとに「続けますか?」と確認する形で同様の安全策になります。
Sub RunWithMsgBoxCancel()
Dim i As Long
Dim response As Integer
For i = 2 To 10000
Cells(i, 2).Value = Cells(i, 1).Value * 2
'500件ごとに確認ダイアログを出す
If i Mod 500 = 0 Then
response = MsgBox(i & "件処理しました。続けますか?", vbYesNo + vbQuestion)
If response = vbNo Then
MsgBox "処理を中断しました。", vbExclamation
Exit For
End If
End If
Next i
End Sub
キャンセル後に元の状態に戻すには?
キャンセルした時点で途中まで書き換えたデータをどう扱うかは、用途によって判断が分かれます。代表的な対処方法を整理します。
| 対処方法 | 内容 | 向いている場面 |
|---|---|---|
| 処理前にシートをバックアップ | ws.Copy で別シートにコピーしてから処理を始める | 元のデータを必ず残したい場合 |
| 書き込み先を別シートにする | 元シートは変えず、結果を別シートに出力する設計にする | 元データを壊したくない場合 |
| 処理済み範囲を記録してクリア | 書き込んだ行数を変数で保持し、キャンセル時にその範囲をクリアする | 中断後に「なかったことにしたい」場合 |
| Undo(Ctrl+Z)を案内する | キャンセルメッセージに「Ctrl+Zで元に戻せます」と案内する | 軽微な変更で手動で戻せる場合 |
まとめ
- VBAのループ処理にキャンセル機能を入れるには ユーザーフォーム・フラグ変数・DoEvents の3つを組み合わせる
frmCancel.Show vbModelessで非モーダル表示にすることで、処理中でもボタン操作ができるようになるDoEventsをループ内に入れることで、ボタンのクリックイベントが処理中でも受け付けられる- フォームを作るのが手間な場合は
MsgBox + vbYesNoによる定期確認で簡易版が作れる - キャンセル後の対応として、処理前のバックアップや書き込み先を別シートにする設計を加えると安全性がさらに上がる
よくある質問
DoEventsを入れると処理が遅くなりますか?
毎回呼び出すと若干の処理コストが発生します。処理が重いループでは If i Mod 100 = 0 Then DoEvents のように一定間隔でのみ呼ぶことで、速度への影響を最小限に抑えられます。データ量や処理内容に合わせて間隔を調整してください。
vbModelessとvbModalの違いは何ですか?
vbModeless(非モーダル)はフォームを開いたままバックグラウンドの処理を続けられます。vbModal(モーダル)はフォームを閉じるまで他の操作ができません。キャンセル機能では処理と並行してボタンを押せる必要があるため、必ず vbModeless を使います。
キャンセルボタンを押しても反応しません
DoEventsがループ内に入っていないか、入っている位置が遠すぎる可能性があります。DoEventsはループの各周回で呼ばれないと、ボタン操作が処理中に届きません。また、処理が1回で非常に重い場合(ファイルを開く・大量のセル書式変更など)は、その処理中はDoEventsが呼べないため反応しないことがあります。
Escキーで強制停止できますか?
VBAではデフォルトでEscキーを押すとマクロが中断されます(「実行を中断しますか?」というダイアログが表示されます)。この動作を利用する場合は Application.EnableCancelKey = xlErrorHandler と組み合わせてエラーハンドラを使う方法もありますが、ユーザーフォームのキャンセルボタンの方が意図が明確でおすすめです。
処理完了後にフォームが残ってしまいます
Unload frmCancel の呼び出しが正しい位置にあるか確認してください。Exit For でループを抜けた後もコードは続くため、キャンセル時・正常完了時いずれのパターンでも Unload frmCancel が実行される位置にまとめて書くのがシンプルです。
動画で学びたい方へ
「記事を読んでも、実際に自分で書けるか不安…」という方には、動画で基礎からじっくり学べる講座がおすすめです。
VBAが初めての方を前提に、つまずきやすいポイントを先回りして解説しています。サンプル動画は無料でご覧いただけます。



