TL;DR —
Workbook_BeforeSaveは、保存が要求された瞬間に — Ctrl+S、[保存] ボタン、あるいはマクロの.Save— ディスクに書き込む前に Excel が発火させる イベントです。渡される引数は 2 つ:SaveAsUI(名前を付けて保存ダイアログが 出ようとしているか?)とCancel(Trueにすれば保存をブロックする)。つまり これは保存操作にかける品質ゲートです:ブックを検査し、書き込みを通すか、拒むか を選びます。誰もがはまるたった 1 つのこと — ハンドラーがThisWorkbook.Saveを 呼ぶと、その保存がまたBeforeSaveを発火させ、Application.EnableEventsで囲まない 限り永遠にループします。
' ThisWorkbook に置く — Module ではない。
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If ThisWorkbook.Sheets("Invoice").Range("Total").Value = "" Then
MsgBox "Enter a Total before saving.", vbExclamation
Cancel = True ' <-- 保存をブロックする。何も書き込まれない
Exit Sub
End If
ThisWorkbook.Sheets("Invoice").Range("LastSaved").Value = Now ' 刻む — ここで .Save は呼ばない
End Sub
保存はユーザーができるいちばん安全なことに感じられます — だからこそ、ルールを強制
するのに正しい場所なのです。Workbook_BeforeSave は、「必須フィールドが空のままでは
保存できない」、自動で刻まれる「最終更新」セル、強制ファイル名、保存時の監査ログの
裏にいるイベントです。同時に、2 つの微妙な罠が潜む場所でもあります:何も起きない
無言の保存と、自分を保存して無限ループに落ちるハンドラーです。
この記事で学べること
- 考え方の軸 — 保存にかけるゲート。
Cancelという拒否権とSaveAsUIという信号つき SaveAsUIが実際に意味すること、そしてそれでファイル名やフォルダーを強制する方法- 自動スタンプのパターン — 「誰が・いつ」を保存されるファイルの中に書き込む
- 再帰の罠 —
BeforeSaveの中の.Saveがループする理由と、EnableEventsの修正 - 無言の
Cancel = Trueが機能ではなくバグである理由
考え方の軸:保存の受領書ではなく、品質ゲート
Workbook_BeforeSave は、保存が要求されたときに発火し、Excel がファイルを書き込む
前に走ります。あなたがそれを呼ぶのではありません — Excel があなたを呼び、待ち、
あなたが何をしたかに基づいて次にどうするかを決めます。2 つの引数を参照渡しで渡します:
SaveAsUI As Boolean— Excel が名前を付けて保存ダイアログを出そうとして いるときTrue(ユーザーが「名前を付けて保存」を選んだか、まったく新しい未保存の ファイルのとき)、既存ファイルへの素の保存ならFalse。これは情報です:これは どの種類の保存か?Cancel As Boolean— あなたの拒否権。Cancel = Trueにすると Excel は保存を 放棄します。何も書き込まれません。
つまりハンドラーはゲートであって、受領書ではありません。受領書なら、保存がすでに
起きたことを確認するものですが、これは先に走り、「ノー」と言えます。(受領書が
欲しいなら — 保存が成功したことを知りたいなら — それは別の Workbook_AfterSave
イベントで、あとから Success フラグとともに発火します。)
シグネチャは固定で、あらゆるブックイベントと同じく ThisWorkbook に置きます:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)。標準の
Module の中では決して発火しません。
役に立たせるルール:Cancel がブロックし、SaveAsUI が方法を決める
2 つの引数は 2 つの異なる仕事をし、これを取り違えるところで人はつまずきます。
Cancel は、前提条件を強制するために使います — データが正しくなるまで保存を
拒むのです:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If Sheets("Order").Range("CustomerID").Value = "" Then
MsgBox "Can't save: Customer ID is required.", vbExclamation
Cancel = True ' 保存はここで止まる
End If
End Sub
SaveAsUI は、ファイルがどう保存されるかを制御するために使います — たいていは、
ユーザーがランダムな名前でコピーをばらまくのを止めるか、命名規則を強制するためです:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If SaveAsUI Then
MsgBox "Use File > Save only. This file must keep its name.", vbCritical
Cancel = True ' 「名前を付けて保存」をブロックし、素の「保存」は許す
End If
End Sub
このハンドラーは通常の Ctrl+S は通しますが、「名前を付けて保存」は拒みます — どの
保存が進行中かを知らなければ不可能な、小さなポリシーです。ルールはこうです:
SaveAsUI が意図を教え、Cancel が結果を決める。
覚える価値のあるパターン:書き込まれる前にファイルへ刻む
BeforeSave がする、いちばん役に立つ 1 つのことは、可能な限り最後の瞬間にメタデータ
をファイルの中に書き込み、保存されるコピーがそれを持ち運ぶようにすることです。
あなたのコードはディスク書き込みの前に走るので、あなたが変えるものは、これから
起きる保存に取り込まれます:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
With Sheets("Log")
.Range("LastSavedBy").Value = Environ("Username")
.Range("LastSavedAt").Value = Now
End With
' ここに .Save はない — このイベントを起こした保存が、そのスタンプを含めてくれる。
End Sub
欠けているものに注目してください:ThisWorkbook.Save の呼び出しがありません。
必要ないのです — 保存はすでに起きつつあり、あなたはディスクに落ちる直前のブックを
編集しているだけです。ここに .Save を足すのは、冗長なだけでなく、次の罠への
お膳立てです。
Excel をフリーズさせる罠:BeforeSave の中で Save を呼ぶ
ハンドラーが ThisWorkbook.Save を呼ぶと(「刻んで、それから保存する」という自然な
本能です)、その保存がまた Workbook_BeforeSave を発火させ、それがまた保存し、
それがまた発火させ — Worksheet_Change がセルに
書き込むのと同じ無限ループの形です。このイベントには、自分自身の動作に反応してしまう
ことへの組み込みのガードがありません。
本当に保存ハンドラーの中から保存を起こさねばならないとき — あるいはそこから書き込む どのイベントでも — その周りでイベントをオフにし、そしてクラッシュがそれをオフのまま 固めないよう、つねにエラーハンドラーの中で元に戻します:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
On Error GoTo CleanExit
Application.EnableEvents = False ' 自分の動作がこのイベントを再発火させないように
' ... それ自体が保存や書き込みをしうる処理 ...
CleanExit:
Application.EnableEvents = True ' 成功しても失敗しても走る
End Sub
Application.EnableEvents は 1 つの、アプリケーション全体のスイッチで、Excel は
それをあなたのために戻しません。ハンドラーがそれを False にして、元に戻す前に
エラーになると、Excel のすべてのイベントが黙り込みます — あなたが再起動するか、
イミディエイトウィンドウで Application.EnableEvents = True を走らせるまで。これは
VBA On Error で説明したのと同じ失敗モードです。
人が忘れるルール:黙ってキャンセルしない
説明のない Cancel = True は、正真正銘のバグです。ユーザーは Ctrl+S を押し、何も
見えず、ファイルは保存されたと信じて立ち去ります — しかし BeforeSave はそっと
それをブロックし、Workbook.Saved はまだ False のままです。何時間もの作業が、次の
クラッシュで蒸発しかねません。Cancel = True にするときはいつでも、MsgBox で
なぜかをユーザーに伝え、ブロックされた保存を無言ではなく目に見える判断にしましょう。
ユーザーに見えないゲートは、罠です。
BeforeSave は保存を守ります。その兄弟 Workbook_BeforeClose
は同じ種類の Cancel 拒否権で閉じる操作を守り、両方とも
Workbook_Open と並んでブックイベントの一族に属します。
ExcelMaster の活用
保存ゲートには、正しいか壊れているかの小さな細部が数多くあります:ThisWorkbook に
置くハンドラー、検証には使うが無言では使わない Cancel、正しい向きで読む SaveAsUI、
.Save の呼び出しなしで書くスタンプ、そして何かが保存するなら EnableEvents の
ガード。
ExcelMaster
なら、代わりにルールを述べられます。「Total セルが空ならこのファイルを保存させないで、
誰がいつ保存したかを刻んで」と言えば、明快なメッセージで保存をブロックし、書き込みの
前にログシートへ刻み、自己発火のループから守る Workbook_BeforeSave を書きます。
ブックもコードもあなたのもの。ルールを独学するときに起こしていたはずのフリーズを
飛ばせるのです。
よくある質問
VBA でブックが保存されるのを止めるには?
Workbook_BeforeSave の中で Cancel 引数を True に設定します:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean) …
Cancel = True。Excel はハンドラーが走ったあとに Cancel を調べます。True なら
保存を放棄し、何も書き込みません。ブロックが無言にならないよう、つねに理由を説明する
メッセージを出しましょう。
SaveAsUI パラメーターは何を意味しますか?
SaveAsUI は、Excel が名前を付けて保存ダイアログを出そうとしているとき True
です — ユーザーが「名前を付けて保存」を選んだか、ファイルが一度も保存されていない
から — そして既存ファイルへの素の保存なら False です。これを読んで 2 つのケースを
別々に扱います。たとえば Ctrl+S は許すが「名前を付けて保存」はブロックして、ファイル
が名前を保つように。
BeforeSave マクロが無限ループを起こすのはなぜですか?
ThisWorkbook.Save(または別の保存)を呼び、その保存がまた Workbook_BeforeSave を
際限なく発火させるからです。たいていは .Save はまったく必要ありません — ハンドラー
の中で加える編集は、すでに進行中の保存に取り込まれます。本当に保存しなければならない
なら、Application.EnableEvents = False … = True で囲み、エラーハンドラーの中で
イベントを元に戻しましょう。
「最終保存」のタイムスタンプを自動で追加するには?
Workbook_BeforeSave の中で、Save を呼ばずに値をセルへ書き込みます:
Sheets("Log").Range("LastSavedAt").Value = Now。ハンドラーは Excel がディスクに
書き込む前に走るので、そのイベントを起こした保存があなたのスタンプを含めます。誰が
保存したかには Environ("Username") を足します。
Workbook_BeforeSave のコードはどこに書きますか?
ThisWorkbook のコードモジュールの中です — プロジェクトエクスプローラーの
「Microsoft Excel Objects」の下で ThisWorkbook をダブルクリックし、
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean) を
貼り付けます。標準の Module からは発火せず、マクロが有効である必要があります。
検証環境
検証環境: Excel 365 (Windows 11), VBA 7.1 — 最終確認 2026-08-05。
関連ガイド: VBA Workbook_BeforeClose · VBA Worksheet_Activate & Deactivate · VBA Workbook_Open · VBA Worksheet_Change · VBA On Error
