「誰がいつ、どのファイルの、どのセルの値を変更したのか」という操作履歴を自動で記録できれば、共同作業の信頼性やデータの監査性が大きく向上します。
Worksheet_Changeイベントは単一のシートしか監視できませんが、VBAのアプリケーションレベルイベントという高度な仕組みを使えば、Excelで開いているすべてのブック・すべてのシートに対する変更を検知し、一つのログシートに記録することが可能です。
この記事では、そのためのVBAコードの全体像と、実装のポイントを解説します。
【重要】コードを記述する場所
今回のコードは、Excelアプリケーション全体の動作を監視する特殊なものです。そのため、すべてのコードを**ThisWorkbookモジュール**に記述する必要があります。標準モジュールやシートモジュールでは正しく動作しません。
- VBE(
Alt+F11)を開きます。 - プロジェクトエクスプローラーから、**
ThisWorkbook**をダブルクリックします。 - 表示されたコードウィンドウに、以下のコードをすべて貼り付けてください。
準備:ログを記録するシート
このマクロは、変更履歴を特定のシートに書き出します。マクロを記述するブックに、あらかじめ「LogSheet」という名前のシートを作成し、1行目に以下のような見出しを設定しておいてください。
| A列 | B列 | C列 | D列 | E列 |
| 変更日時 | ブック名 | シート名 | セル番地 | 変更後の値 |
完成したVBAコード(全体像)
'==================================================
' ThisWorkbookモジュールに、以下のコードをすべて記述します
'==================================================
'--- 1. イベントを検知するための変数を宣言 ---
' WithEventsキーワードが、この変数を特別なイベント変数にします
Private WithEvents AppEvents As Excel.Application
'--- 2. ブックを開いたときにイベント監視を開始 ---
Private Sub Workbook_Open()
' アプリケーションオブジェクトをイベント変数にセット
Set AppEvents = Application
End Sub
'--- 3. ブックを閉じる前にイベント監視を終了 ---
Private Sub Workbook_BeforeClose(Cancel As Boolean)
' イベント変数への参照を解除
Set AppEvents = Nothing
End Sub
'--- 4. セルが変更された瞬間に、このイベントが実行される ---
Private Sub AppEvents_SheetChange(ByVal Sh As Object, ByVal Target As Range)
' 変数を宣言
Dim logSheet As Worksheet
Dim nextRow As Long
' ログを記録するシートを指定
On Error Resume Next ' ログシートがない等のエラーを回避
Set logSheet = ThisWorkbook.Worksheets("LogSheet")
If logSheet Is Nothing Then Exit Sub
On Error GoTo 0
'【無限ループ防止①】変更がログシート自身で起きた場合は記録しない
If Sh.Name = logSheet.Name Then Exit Sub
' ログシートの最終行を取得
nextRow = logSheet.Cells(logSheet.Rows.Count, "A").End(xlUp).Row + 1
'【無限ループ防止②】ログ書き込み処理中は、一時的にイベントをオフにする
Application.EnableEvents = False
' ログシートに変更履歴を書き込む
With logSheet.Cells(nextRow, "A")
.Value = Now() ' 日時
.Offset(0, 1).Value = Sh.Parent.Name ' ブック名
.Offset(0, 2).Value = Sh.Name ' シート名
.Offset(0, 3).Value = Target.Address(False, False) ' セル番地
.Offset(0, 4).Value = Target.Value2 ' 変更後の値
End With
' イベントを再度有効化
Application.EnableEvents = True
End Sub
コードのポイント解説
① WithEvents での変数宣言
Private WithEvents AppEvents As Excel.Application WithEventsキーワードを付けて変数を宣言することで、その変数がExcelアプリケーションの様々なイベント(セルの変更、ブックを開くなど)を「傍受」できるようになります。
② イベント監視の開始と終了
Workbook_Openイベント内でSet AppEvents = Applicationを実行することで、このブックが開かれている間、AppEvents変数がExcel全体の動作の監視を開始します。逆にWorkbook_BeforeCloseでSet AppEvents = Nothingとして監視を終了させます。
③ 変更イベントの捕捉 (AppEvents_SheetChange)
Private Sub AppEvents_SheetChange(...) これが、実際にセルが変更されたときに実行されるメインの処理です。プロシージャ名は**「WithEventsで宣言した変数名」 + _ + 「イベント名」**というルールで決まっています。
Sh As Object: 変更が加えられたシートのオブジェクトが渡されます。Target As Range: 変更されたセル範囲のオブジェクトが渡されます。
④ 無限ループの防止
このコードで最も重要なのが、無限ループを防ぐ2つの仕組みです。
- ログシート自身の変更は記録しない: ログを書き込む行為自体も「セルの変更」イベントを発生させます。
If Sh.Name = logSheet.Name Then Exit Subという一文で、ログシートへの書き込みが、さらなるログ記録を呼び出すのを防いでいます。 - イベントの一時的な無効化: より確実に連鎖を断ち切るため、ログを書き込む処理の前後を
Application.EnableEvents = False / Trueで囲んでいます。
まとめ
アプリケーションレベルのイベントを利用することで、個別のブックやシートにマクロを仕込むことなく、Excel全体の操作を監視する高度なツールを作成できます。
ThisWorkbookモジュールにWithEvents付きの変数を宣言する。Workbook_Openで、その変数にApplicationをセットして監視を開始する。変数名_イベント名という名前のプロシージャ内に、実行したい処理を記述する。- イベントの連鎖(無限ループ)が発生しないよう、細心の注意を払う。
このテクニックは高度ですが、ブックの監査ログの作成や、特定の操作を全ブックで共通して制御したい場合などに、非常に強力な解決策となります。
