TL;DR —
Worksheet_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)。(すべてのシートを
一度に対象とするブック全体版は、ThisWorkbook の Workbook_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+B1 で A1 が変わると、
イベントは(編集された)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
からは動きません。すべてのシートを一度に対象とするなら、ThisWorkbook の
Workbook_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
