TL;DR —
Worksheet_Activateは、シートがアクティブになったときに発火します (あなたがそこへ着地する)。Worksheet_Deactivateは、あなたがそのシートを離れる ちょうどそのとき(別のシートが引き継ぐ前)に発火します。これらはシートにとっての 到着と出発のイベントです —Activateで表示時にダッシュボードをリフレッシュし、Deactivateでユーザーが先へ進む前に物事を確かめます。両者を定義づける勘所: どちらにもCancel引数がありません。 BeforeClose や BeforeSave と違い、シートの切り替えを見ることはできても、ブロックはできません — できる 最善は、ユーザーを押し戻すことです。
' シート自身のモジュール(例:"Dashboard")に置く — ThisWorkbook でも Module でもない。
Private Sub Worksheet_Activate()
Me.Range("A1").Select ' 到着するたびに先頭へ移動する
ActiveWindow.ScrollRow = 1
Me.Calculate ' 表示時にこのシートの数値をリフレッシュする
End Sub
Private Sub Worksheet_Deactivate()
' ここには Cancel パラメーターが存在しない — 警告はできても、離脱は止められない。
If Me.Range("Signoff").Value = "" Then _
MsgBox "Heads up: you left the Dashboard without signing off.", vbInformation
End Sub
シートを切り替えることは、ユーザーがするいちばんありふれた行為であり、この 2 つの
イベントは、シートがそれに反応することを可能にします。Worksheet_Activate は
「このタブを見たらリフレッシュする」を支えます — 再計算、再クエリ、先頭へスクロール、
保護・保護解除。Worksheet_Deactivate は「立ち去る前に確かめる」を支えます。コツは、
これらのイベントにできないことを理解することです。それこそが、これらを門番イベント
から分けるものだからです。
この記事で学べること
- 考え方の軸 — 到着と出発のイベント、シートごとに 1 組
Cancelがない理由 — そしてユーザーを引き止めたいときの押し戻しの回避策- 表示時にリフレッシュするパターン(そして「開いたときに走らなかった」落とし穴)
- シートレベルの
Worksheet_Activate対ブックレベルのWorkbook_SheetActivate - グラフシートと
EnableEventsのガードが、ここで両方とも効いてくる理由
考え方の軸:到着と出発のゲート
各シートを、空港のゲートだと思い浮かべてください。Worksheet_Activate は、あなたが
このゲートに歩み入った瞬間に発火します — シートがいまアクティブになったのです。
Worksheet_Deactivate は、あなたが歩み出るときに発火します — 別のタブをクリック
し、このシートが引き継ごうとしているのです。どちらもあなたが呼ぶことはありません。
Excel が切り替えのときに、順に発火させます:まず古いシートの Deactivate、それから
新しいシートの Activate です。
両方とも引数なしで、両方ともその特定のシートのコードモジュールに置かれます —
ThisWorkbook でも、標準の Module でもありません。「Microsoft Excel Objects」の下で
シート(たとえば Dashboard)をダブルクリックし、そこに
Private Sub Worksheet_Activate() を書きます。中では Me がそのシートを指すので、
Me.Calculate や Me.Range(...) がすっきり読めます。
これらのイベントを定義づけるルール:拒否権はない
これが、このクラスター全体を束ねる洞察です。BeforeClose と BeforeSave は、それぞれ
Cancel フラグを手渡します — ハンドラーは、その動作を止められるチェックポイント
です。Worksheet_Activate と Worksheet_Deactivate には、そのようなパラメーターが
ありません。 シグネチャを見てください:Worksheet_Deactivate() は何も取りません。
設定すべき Cancel がないので、ユーザーがシートを離れるのを防ぐ方法はありません。
Deactivate が走るときには、切り替えの決定はすでに下されています。
これは見落としではありません — シートの切り替えは、閉じたり保存したりするような、 リスクのある取り返しのつかない行為ではないので、Excel は拒否権を差し出しません。 けれどそれは、「B2 が埋まるまでこのタブを離れさせない」が直接には不可能だという ことを意味します。正直な回避策は押し戻しです:問題を検出してシートを再アクティブ にし、再発火からガードします:
Private Sub Worksheet_Deactivate()
If Me.Range("B2").Value = "" Then
Application.EnableEvents = False ' 下の Activate が連鎖しないように
Me.Activate ' ユーザーをこのシートへ引き戻す
Application.EnableEvents = True
MsgBox "Fill in B2 before leaving this sheet.", vbExclamation
End If
End Sub
これが何であるかを理解してください:キャンセルされた切り替えではなく、完了した
切り替えの直後に続く、戻る切り替えです。ちらつき、本物の拒否権より重く、イベントを
再発火させます — だから Application.EnableEvents のガードがあります。これは
Worksheet_Change で使うのと同じ規律です。データが
正しくなるまで本当にユーザーを引き止める必要があるなら、シートイベントと戦うより、
モーダルで表示した UserForm のほうがきれいな道具です。限界を知れば、存在しない
Cancel を追いかける代わりに、その周りを設計できます。
Activate を値打ちにするパターン:表示時にリフレッシュ
Worksheet_Activate の最良の使い道は、「誰かが見た瞬間に、このシートを正しくする」
です。サマリータブが再計算し、レポートがソースを再クエリし、データ入力シートが先頭へ
スクロールして入力セルの保護を解除します:
Private Sub Worksheet_Activate()
Me.Unprotect Password:="x"
Me.Range("Inputs").Locked = False
Me.Calculate
Application.Goto Me.Range("A1"), Scroll:=True ' 到着時に A1 を左上に置く
End Sub
「Activate が走らなかった」という質問を絶えず生む落とし穴が 1 つあります:
ブックが開いたときにすでにアクティブなシートは、Worksheet_Activate を発火させ
ません — そこへ切り替えられたのではなく、単に開始シートだったからです。その最初の
シートで開いた時点でリフレッシュを起こす必要があるなら、Workbook_Open
からも走らせるか、リフレッシュルーチンを両方から呼びましょう。
正しく捉える区別:シートレベル対ブックレベル
シートモジュールの中の Worksheet_Activate は、その 1 枚のシートに対してだけ発火
します。ユーザーがどのシートに着地しても反応したいなら、同じハンドラーを 12 枚の
シートモジュールに貼り付けるのはやめて — ThisWorkbook のブックレベルのイベントを
使います:
' ThisWorkbook に置く — すべてのシートで発火し、どのシートかを Sh で教える。
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Application.StatusBar = "You are on: " & Sh.Name
End Sub
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
' Sh は離れようとしているシート。
End Sub
ブック版は Sh — 関わっているシート — を手渡すので、1 つのハンドラーがファイル全体を
カバーします。ルールはこうです:特定の 1 枚のシート → そのシートの
Worksheet_Activate。どのシートでも → ThisWorkbook の Workbook_SheetActivate。
ブックレベルのつもりでシートレベルを選ぶ(またはその逆)ことが、「イベントが間違った
タブで発火する」いちばんよくある理由です。
もう 2 つ、知っておく価値のある境目。Worksheet_Activate はワークシート専用です —
グラフシートを選ぶと、このイベントではなく Chart_Activate が発生するので、これを
期待するハンドラーは無言のまま決して走りません。そして、これらのハンドラーの中でする
どんな書き込みや選択も、さらなるイベントへ連鎖しかねません。軽く保ち、ブックを変える
ときは Application.EnableEvents に手を伸ばしましょう。
ExcelMaster の活用
シート切り替えイベントは単純に見えて、本物の判断を隠しています:どのイベントか(到着
対出発)、どのレベルか(シート対ブック)、離脱をキャンセルできないという事実、開いた
ときの最初のシートの落とし穴、そしてどんな押し戻しにも EnableEvents のガード。
ExcelMaster
なら、代わりに結果を述べられます。「Dashboard タブを開くたびに、再計算して先頭へ
スクロールして」と言えば、正しいシートに Worksheet_Activate を書きます — そして
「サインオフせずに離れたら警告して」と頼めば、ブロックできるふりをするのではなく
警告する Worksheet_Deactivate を足します。できないからです。ブックもコードもあなたの
もの。限界を痛い目で学ぶ部分だけを飛ばせるのです。
よくある質問
Worksheet_Activate と Worksheet_Deactivate の違いは何ですか?
Worksheet_Activate は、シートがアクティブになったときに走ります — いまそこへ切り
替えたのです。Worksheet_Deactivate は、そのシートを離れるとき、別のシートが
アクティブになる直前に走ります。切り替えのとき、Excel はまず古いシートの Deactivate、
それから新しいシートの Activate を発火させます。両方とも特定のシートのコード
モジュールに置かれ、引数を取りません。
VBA でユーザーがワークシートを離れるのを止められますか?
直接にはできません — Worksheet_Deactivate には Cancel パラメーターがないので、
Workbook_BeforeClose が閉じる操作を拒否するようには、切り替えを拒否できません。
回避策は押し戻しです:Deactivate の中で Me.Activate を呼んでユーザーをシートへ
戻し(Application.EnableEvents = False … = True でガードする)、それからメッセージを
出します。本物のブロックには、代わりにモーダルの UserForm を表示しましょう。
ブックを開いたとき Worksheet_Activate が走らないのはなぜですか?
開始シートはそこへ切り替えられたのではなく — ファイルが開いたときにすでにアクティブ
だったので、そのシートにはアクティブ化イベントが発火しないからです。別のシートへ移って
戻れば発火しますが、開いた時点での最初のシートには、リフレッシュを Workbook_Open
からも走らせましょう。
1 枚だけでなく、どのシートが選ばれてもコードを走らせるには?
ThisWorkbook のブックレベルのイベントを使います:
Private Sub Workbook_SheetActivate(ByVal Sh As Object) は、すべてのシートで発火し、
アクティブになったシートを Sh として渡します。Worksheet_Activate をすべての
シートモジュールにコピーするより、これのほうがきれいです。
Worksheet_Activate のコードはどこに書きますか?
特定のワークシートのコードモジュールの中です — プロジェクトエクスプローラーの
「Microsoft Excel Objects」の下でそのシート(例:Sheet1 や Dashboard)をダブル
クリックし、Private Sub Worksheet_Activate() を追加します。標準の Module からは
発火しません。すべてのシート版の Workbook_SheetActivate は ThisWorkbook に置きます。
検証環境
検証環境: Excel 365 (Windows 11), VBA 7.1 — 最終確認 2026-08-05。
関連ガイド: VBA Workbook_BeforeClose · VBA Workbook_BeforeSave · VBA Workbook_Open · VBA Worksheet_SelectionChange · VBA On Error
