ExcelVBAでWorksheet_Changeイベントを使ってセルの変更を検知する処理を書いていると、「イベント内でセルの値を書き換えたら、Excelが固まったり、同じ処理が何度も実行されてしまった」という経験をしたことがあるかもしれません。これは、イベント処理の中でセルを変更したことが再びChangeイベントを発生させてしまう「無限ループ」が原因です。
この無限ループを防ぐために使うのが、Application.EnableEventsプロパティです。この記事では、無限ループが起きる仕組みから、EnableEventsを使った正しい防止方法、エラーが起きても安全に元へ戻すための書き方まで解説します。
Worksheet_Changeで無限ループが起きる仕組み
まずは、無限ループが起きてしまう典型的なコード例を見てみましょう。
' 悪い例:無限ループが発生するコード
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 Then
' A列が変更されたら、B列に更新日時を記録する
Target.Offset(0, 1).Value = Now
End If
End Sub
一見問題なさそうに見えますが、このコードには落とし穴があります。Target.Offset(0, 1).Value = NowでB列のセルを書き換えると、そのセル変更によって再びWorksheet_Changeイベントが発生します。B列への書き込みはTarget.Column = 1の条件に当てはまらないため実際には無限ループにはなりませんが、条件の書き方や参照範囲によっては、書き換えたセル自身が再び条件に一致してしまい、イベントが延々と発生し続けることがあります。
特に、変更対象のセル範囲に自分自身が含まれるような処理(例えば列全体を対象にした処理や、複雑な条件分岐)では、この無限ループが起きやすくなります。
Application.EnableEventsの使い方
Application.EnableEventsは、Excelのイベント処理を一時的に無効化するプロパティです。Falseに設定するとイベントが発生しなくなり、Trueに戻すとイベントが再び有効になります。
イベント処理内でセルを書き換える前にEnableEventsをFalseにしておけば、その書き換えによって同じイベントが再発生することを防げます。
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 Then
Application.EnableEvents = False
' A列が変更されたら、B列に更新日時を記録する
Target.Offset(0, 1).Value = Now
Application.EnableEvents = True
End If
End Sub
このように、セルを書き換える処理をApplication.EnableEvents = FalseとApplication.EnableEvents = Trueで挟むことで、書き換え中はイベントが発生しなくなり、無限ループを確実に防止できます。
On Errorと組み合わせた安全な書き方
EnableEvents = Falseにした後、途中でエラーが発生してプロシージャが異常終了してしまうと、EnableEvents = Trueに戻す処理が実行されないままになります。そうなると、それ以降すべてのイベントが発生しなくなり、「マクロが全く反応しない」という不具合につながります。
これを防ぐために、On Errorステートメントと組み合わせて、エラーが起きても必ずEnableEventsをTrueに戻すようにします。
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo ErrHandler
If Target.Column = 1 Then
Application.EnableEvents = False
Target.Offset(0, 1).Value = Now
End If
ErrHandler:
Application.EnableEvents = True
End Sub
On Error GoTo ErrHandlerによって、途中でエラーが発生しても必ずErrHandlerラベル以降のApplication.EnableEvents = Trueが実行されます。イベントプロシージャでEnableEventsを操作するときは、このように「必ず元に戻す」処理をセットで書くことを習慣にしておくと安全です。
実務例:変更履歴を記録しつつ無限ループを防ぐ
セルが変更されたときに、別シートへ変更履歴(変更前の値・変更後の値・変更日時)を記録するマクロも、EnableEventsがないと無限ループになりやすい代表例です。
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo ErrHandler
Dim logSheet As Worksheet
Set logSheet = ThisWorkbook.Worksheets("変更履歴")
Dim nextRow As Long
nextRow = logSheet.Cells(logSheet.Rows.Count, 1).End(xlUp).Row + 1
Application.EnableEvents = False
' 変更履歴シートへ記録(この書き込み自体はEnableEvents=Falseなので再発火しない)
logSheet.Cells(nextRow, 1).Value = Now
logSheet.Cells(nextRow, 2).Value = Target.Address
logSheet.Cells(nextRow, 3).Value = Target.Value
ErrHandler:
Application.EnableEvents = True
End Sub
このコードでは、変更履歴シートへの書き込み自体はセルの変更を伴う処理ですが、EnableEvents = Falseにしてから書き込んでいるため、履歴記録用の書き込みが新たなWorksheet_Changeイベントを引き起こすことはありません。
まとめ
Worksheet_Changeイベント内でセルを書き換えると、その書き換えが再びChangeイベントを発生させ、無限ループの原因になることがあるApplication.EnableEvents = Falseにしてからセルを書き換え、処理後にTrueに戻すことで、無限ループを防止できるEnableEvents = Trueに戻す処理は、On Errorと組み合わせてエラー時にも必ず実行されるようにしておくと安全。戻し忘れると、マクロ全体が反応しなくなる不具合につながる
Worksheet_Changeをはじめとするイベントプロシージャ内でセルや値を書き換える処理を行う場合は、EnableEventsの制御を必ずセットで検討する習慣をつけておきましょう。


コメント