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

Excel VBA の Workbook_BeforeSave — 保存を検証・ブロックし、SaveAsUI が本当に伝えていること

|

Excel VBA の Workbook_BeforeSave — 保存を検証・ブロックし、SaveAsUI が本当に伝えていること

TL;DRWorkbook_BeforeSave は、保存が要求された瞬間に — Ctrl+S、[保存] ボタン、あるいはマクロの .Save — ディスクに書き込むに Excel が発火させる イベントです。渡される引数は 2 つ:SaveAsUI(名前を付けて保存ダイアログが 出ようとしているか?)と CancelTrue にすれば保存をブロックする)。つまり これは保存操作にかける品質ゲートです:ブックを検査し、書き込みを通すか、拒むか を選びます。誰もがはまるたった 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