「誰がいつ、どのファイルの、どのセルの値を変更したのか」という操作履歴を自動で記録できれば、共同作業の信頼性やデータの監査性が大きく向上します。
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
をセットして監視を開始する。変数名_イベント名
という名前のプロシージャ内に、実行したい処理を記述する。- イベントの連鎖(無限ループ)が発生しないよう、細心の注意を払う。
このテクニックは高度ですが、ブックの監査ログの作成や、特定の操作を全ブックで共通して制御したい場合などに、非常に強力な解決策となります。