🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

Excel VBA の Worksheet_Change — セルが編集されたときにコードを走らせる(そして避けるべき無限ループ)

|

Excel VBA の Worksheet_Change — セルが編集されたときにコードを走らせる(そして避けるべき無限ループ)

TL;DRWorksheet_Change は、ユーザー(またはあなたのコード)がそのシートの セルを編集するたびに Excel が発火させるイベントです。Excel は変更されたセルを Target として手渡すので、それに反応できます — 行にタイムスタンプを打つ、入力を 検証する、編集を記録する。知っておくべきたった 1 つのこと:ハンドラーがセルに 書き込むと、その書き込みが Worksheet_Change をまた発火させます — そしてまた — Excel がフリーズするまで。解決策は、すべての書き込みを囲むたった 1 組の行、 Application.EnableEvents = False … = True です。この反射さえ身につければ、あとは すべて細部です。

' シート自身のオブジェクト(例:Sheet1)に置く。Module や ThisWorkbook ではない。
Private Sub Worksheet_Change(ByVal Target As Range)
    If Intersect(Target, Range("B:B")) Is Nothing Then Exit Sub   ' B 列にだけ反応する
    On Error GoTo Done
    Application.EnableEvents = False          ' 自分の書き込みがこのイベントを再発火させないように
    Target.Offset(0, 1).Value = Now          ' 編集の隣に時刻を刻む
Done:
    Application.EnableEvents = True           ' イベントは必ずオンに戻す
End Sub

編集に反応するところで、VBA は「マクロを走らせるボタン」であることをやめ、生きて いるように感じられ始めます:セルが変わると、シートが自分で応じるのです。 Worksheet_Change は、監査証跡、自動タイムスタンプ、リアルタイムの検証、連動 ドロップダウンを支えるイベントです。同時に、最もうろたえる「Excel がフリーズして 作業を失った」瞬間を生むイベントでもあります — 素直に書いたコードが、無限ループの ほんの一歩手前だからです。

この記事で学べること

  • 考え方の軸 — 変更されたセルを Target として手渡す編集センサー
  • 無限ループの罠 — そしてイベント VBA を定義づける Application.EnableEvents の解決策
  • クラッシュが、Excel を再起動するまですべてのイベントをオフにしかねない理由
  • すべての編集で走らないよう Intersect でハンドラーのスコープを絞る方法
  • 数式の再計算を無視する理由 — そして代わりに Worksheet_Calculate が必要になるとき

考え方の軸:ボタンではなく、編集センサー

Worksheet_Change は、シートに配線されたセンサーです。あなたがそれを呼ぶのでは ありません。セルの内容が変わった瞬間に、Excel があなたを呼び、変更されたセル を Target という名の Range として渡します。あなたの仕事は、Target を読んで、 それにどう対処するかを決めることです。

この捉え方が 2 つのことを片づけます。第一に、Target があなたの入力のすべてです — それは編集が起きた場所であり、1 つのセルのことも、多数のこともあります(貼り付け、 フィル、選択範囲をまたぐ削除は、すべて複数セルの Target として届きます)。第二に、 ハンドラーはその特定のシートのコードオブジェクト — プロジェクトエクスプローラー の Sheet1 — に置かれ、ThisWorkbook でも Module でもありません。シグネチャは 固定です:Private Sub Worksheet_Change(ByVal Target As Range)。(すべてのシートを 一度に対象とするブック全体版は、ThisWorkbookWorkbook_SheetChange です。)

Excel をフリーズさせる罠:あなたの書き込みがイベントを再発火させる

これは、あらゆる VBA 開発者がちょうど一度だけ書くバグです。編集に反応して、何かを シートに書き戻したいとします:

Private Sub Worksheet_Change(ByVal Target As Range)
    Target.Offset(0, 1).Value = Now   ' 編集の隣に書く…それ自体がまた編集
End Sub

Target.Offset(0, 1) への書き込みはセルを変えます — それが Worksheet_Change を また発火させ — その書き込みがまた発火させ — 永遠に。Excel はスタックを使い果たして 例外を投げるまで再帰するか、あるいは単にフリーズしたように見えます。変更イベントに は、自分自身の反応に反応してしまうことに対する組み込みのガードがありません。

解決策は、イベント VBA でもっとも重要なイディオムです:あらゆる書き込みの前後で イベントをオフにし、それからまたオンに戻す。

Private Sub Worksheet_Change(ByVal Target As Range)
    Application.EnableEvents = False
    Target.Offset(0, 1).Value = Now
    Application.EnableEvents = True
End Sub

EnableEvents = False にしておけば、あなたの書き込みは新しい Worksheet_Change を 引き起こさないので、再帰はありません。そのあと元に戻します。ルールを焼き付け ましょう:セルに書き込むイベントの中では、その書き込みを Application.EnableEvents = False … = True で囲む。

罠の後半を隠すルール:クラッシュはイベントをオフのまま残す

Application.EnableEvents は 1 つの、アプリケーション全体のスイッチで、Excel は それを自動では戻しません。それが厄介な第二の失敗モードを生みます:ハンドラーが False に設定した、それを戻すにエラーになると、イベントはオフのまま — Excel セッション全体、すべてのシートとブックにわたって — 残ります。

症状は人を戸惑わせます:「マクロが急に動かなくなった」。あなたの Worksheet_Change が壊れたのではありません — 以前の実行がハンドラーの途中で死んだせいで、イベントが 全体的に抑制されているのです。だから安全なパターンは、つねにエラーハンドラーの中で イベントを再び有効にします:

Private Sub Worksheet_Change(ByVal Target As Range)
    On Error GoTo CleanExit
    Application.EnableEvents = False
    Target.Offset(0, 1).Value = Now
    ' ... エラーになりうる、さらなるロジック ...
CleanExit:
    Application.EnableEvents = True     ' 成功しても失敗しても走る
End Sub

これはまさに、VBA On Error の「必ず後始末をする 1 つの 出口」の心得であり、ここでは譲れません。(すでにイベントがオフのまま固まって いるなら、イミディエイトウィンドウで 1 行 — Application.EnableEvents = True — を 走らせるか、単に Excel を再起動しましょう。)

対象を絞るルール:Intersect でスコープを切る

素の Worksheet_Change は、シート上のどのセルに対しても発火します。1 つの列や 1 つのテーブルの編集だけを気にかけるなら、それを言わねばなりません — さもないと ハンドラーは、無関係な編集すべてで走り(そしておそらく書き込み)ます。その道具が Intersect です。これは Target と、あなたが気にかける範囲との重なりを返し、 重なりがなければ Nothing を返します:

Private Sub Worksheet_Change(ByVal Target As Range)
    ' B2:B1000 の外の編集は無視する
    If Intersect(Target, Range("B2:B1000")) Is Nothing Then Exit Sub
    ' ... 重要な部分にだけ反応する ...
End Sub

関連する 2 つの注意。Target多数のセルでありえます — 貼り付けや列フィルは 複数セルの範囲を手渡すので、Target.Value が単一の値だと思い込むコードはエラーに なるか誤動作します。Target をループするか、本当に単一の編集だけを扱いたいなら If Target.Count > 1 Then Exit Sub で制限しましょう。そして、貼り付けが監視範囲と その外にまたがるときは、Target 全体ではなく重なりに反応すること。

人がつまずく区別:数式の再計算は無視される

Worksheet_Change は、セルの内容の変化 — 打ち込まれた値、貼り付けられた値、 削除、VBA による書き込み — で発火します。数式が再計算したせいでセルが新しい数を 表示しただけのときは、発火しませんC1=A1+B1A1 が変わると、 イベントは(編集された)A1 に対して発火し、(再計算された)C1 に対しては 発火しません。

結果の変化に反応する必要があるなら、それは別のイベント — Worksheet_Calculate — です。これは再計算時に発火しますが、Target を渡しません(セルは自分で調べる 必要があります)。間違った方を選ぶのは静かな失敗です:ハンドラーが単に一度も 走りません。ルールはこうです:Change = 誰かがセルを編集した。Calculate = 数式の出力が動いた。

Worksheet_Change は「ユーザーに反応する」ペアの片割れです。その兄弟 Worksheet_SelectionChange は、値が変わった ときではなくカーソルが動いたときに発火します — そして両者は、ファイルが最初に 開くときに走るイベント Workbook_Open と同じイベント 一族に属します。

ExcelMaster の活用

編集に反応するマクロは、見かけによらず細かい注文の多いものです:正しいオブジェクト、 スコープを絞るための Intersect、ループを避けるための EnableEvents、クラッシュが Excel のあらゆるイベントを殺さないためのエラーハンドラー。EnableEvents のペアを 忘れればファイルをフリーズさせ、エラーハンドラーを忘れれば無言でイベントを壊します。

ExcelMaster なら、代わりに振る舞いを述べられます。「誰かが B 列を編集したら、その隣の C 列に 現在時刻を入れて」と言えば、正しいシートオブジェクトに Worksheet_Change を 書きます — Intersect でスコープを絞り、EnableEvents でガードし、エラーが イベントをオフのまま固めないように包んで。シートもコードもあなたのものです。自分の ブックをフリーズさせてルールを学ぶ、あの一度きりの儀式だけを飛ばせるのです。

よくある質問

Worksheet_Change が無限ループやフリーズを起こすのはなぜですか?

ハンドラーがセルに書き込み、その書き込み自体が変更となって Worksheet_Change を また発火させるからです — 際限なく。すべての書き込みを、その前の Application.EnableEvents = False と後の Application.EnableEvents = True で囲み、 さらにエラーハンドラーの中でつねに再有効化して、クラッシュがイベントをオフのまま 残せないようにしましょう。

特定の列が変更されたときだけ Worksheet_Change を走らせるには?

ハンドラーの先頭で Intersect を使います: If Intersect(Target, Range("B:B")) Is Nothing Then Exit Sub。これは、編集が B 列に 触れていない限り、直ちに抜けます。Intersect は、編集された範囲とあなたが気にかける 範囲との重なりを返し、重なりがなければ Nothing を返します。

数式が再計算されると Worksheet_Change は発火しますか?

いいえ。発火するのは、セルの内容が編集された — 打ち込まれた、貼り付けられた、 削除された、VBA で書き込まれた — ときだけです。数式の再計算のせいで新しい値を表示 するセルは、それを引き起こしません。そのためには Worksheet_Calculate イベントを 使います。これは再計算時に発火しますが、Target を渡しません。

Worksheet_Change のコードはどこに書きますか?

その特定のワークシートのコードモジュールの中です — プロジェクトエクスプローラーの 「Microsoft Excel Objects」の下でシート(例:Sheet1)をダブルクリックし、そこに Private Sub Worksheet_Change(ByVal Target As Range) を置きます。標準の Module からは動きません。すべてのシートを一度に対象とするなら、ThisWorkbookWorkbook_SheetChange を使います。

VBA のイベントが動かなくなりました — どう直しますか?

おそらく以前のハンドラーが Application.EnableEvents = False を設定し、戻す前に エラーになって、イベントを全体的にオフのまま残したのです。イミディエイトウィンドウ (Ctrl+G)で Application.EnableEvents = True と打って Enter を押すか、Excel を 再起動します。つねにエラーハンドラーの中でイベントを再有効化して、これを防ぎましょう。

検証環境

検証環境: Excel 365 (Windows 11), VBA 7.1 — 最終確認 2026-08-02。

関連ガイド: VBA Worksheet_SelectionChange · VBA Workbook_Open · VBA On Error · VBA Range · VBA For Loop