VBAで処理を途中でキャンセルする方法 | ユーザーフォーム・DoEvents・簡易MsgBox版

VBAのマクロは一度実行すると途中で止められませんが、ユーザーフォームのキャンセルボタン+フラグ変数+DoEventsを組み合わせることで、処理中でもユーザーが中断できる仕組みを作れます。大量データの処理中に「間違えた」と気づいたときのための安全策として、ぜひ取り入れておきましょう。

この記事では、次の内容を順番に解説します。

  • キャンセル機能の仕組みとしくみ全体の流れ
  • ユーザーフォームとキャンセルボタンの作り方
  • フラグ変数とDoEventsを使ったメイン処理のコード
  • フォームを使わずMsgBoxだけで実現する簡易版
  • キャンセル後のロールバック(元に戻す処理)の考え方

キャンセル機能の仕組みを理解するには?

VBAのループ処理中にキャンセルを実現するには、次の3つの要素を組み合わせます。

要素役割
ユーザーフォーム(キャンセルボタン)ユーザーが「やめる」と操作できる画面を用意する
フラグ変数(Public CancelFlg)ボタンが押されたことをメイン処理に伝える
DoEventsループ中でもボタン操作などの外部イベントを受け付けるようにする

処理の流れは次のとおりです。

  1. 処理開始と同時にキャンセルボタンつきのフォームを非モーダル表示する
  2. ループの各周回で DoEvents を呼び、ボタン操作を受け付ける
  3. ユーザーがキャンセルボタンを押すと CancelFlg = True になる
  4. 次のループで CancelFlgTrue になっていたら Exit For で処理を抜ける

ユーザーフォームとキャンセルボタンを作るには?

まずVBEでユーザーフォームを作成し、キャンセルボタンを配置します。

  1. VBEのメニューから「挿入」→「ユーザーフォーム」を選ぶ
  2. プロパティウィンドウで名前を frmCancel に変更する
  3. ツールボックスからコマンドボタンをフォームに配置する
  4. ボタンの名前を cmdCancel、キャプションを「キャンセル」に変更する

次に、標準モジュールにフラグ変数を宣言します。

'標準モジュールの先頭に記述する
Public CancelFlg As Boolean

フォームのキャンセルボタンに次のコードを記述します(frmCancel のコードウィンドウを開いて入力)。

Private Sub cmdCancel_Click()
    CancelFlg = True   'フラグをTrueにしてメイン処理に伝える
    Unload Me          'フォームを閉じる
End Sub

メイン処理にキャンセルチェックを組み込むには?

メイン処理のループ内に DoEventsCancelFlg のチェックを入れます。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が初めての方を前提に、つまずきやすいポイントを先回りして解説しています。サンプル動画は無料でご覧いただけます。

動画で学ぶExcelマクロ|JIMOVEオンラインスクール

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール