「開いているすべてのブックでシートが変更されたら記録したい」「Workbook_OpenやWorksheet_Changeのようなイベントを、あとから追加したブックにも仕込みたい」——こうした要望は、ThisWorkbookモジュールやSheetモジュールに直接コードを書くだけでは実現できません。そんなときに使うのがWithEventsキーワードです。
この記事では、クラスモジュールとWithEventsを使ってイベントを検知する基本の仕組みから、複数ブックのイベントをまとめて監視する実践的なコード例、つまずきやすい注意点まで解説します。
WithEventsとは何か
通常、Workbook_OpenやWorksheet_Changeのようなイベントプロシージャは、ThisWorkbookモジュールやSheetモジュールにあらかじめ用意された枠にコードを書くことで動作します。しかしこの方法は、あくまで「そのブック・そのシート」に対してしか使えません。
WithEventsキーワードをクラスモジュールで使うと、実行時に代入したオブジェクト(Application、Workbook、Worksheetなど)のイベントを、自分で作ったクラスの中で自由に受け取れるようになります。これにより、複数のブックやシートに対して同じイベント処理を動的に適用できます。
基本の書き方
WithEventsはクラスモジュール内でのみ使用できます。標準モジュールでは使えない点に注意してください。
' クラスモジュール「clsAppEvents」
Public WithEvents App As Application
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
Debug.Print Wb.Name & " が開かれました"
End Sub
Private Sub App_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Debug.Print Sh.Name & " のセルが変更されました:" & Target.Address
End Sub
Public WithEvents App As Applicationと宣言すると、コードウィンドウ左上のオブジェクト一覧に「App」が表示され、右上のプロシージャ一覧から利用可能なイベントを選択できるようになります。
このクラスを使うには、標準モジュール側でインスタンスを作成し、AppプロパティにApplicationオブジェクトを代入します。
' 標準モジュール
Public appEvents As New clsAppEvents
Sub InitAppEvents()
Set appEvents.App = Application
Debug.Print "イベント監視を開始しました"
End Sub
InitAppEventsを実行すると、以降はどのブックでシートが変更されてもApp_SheetChangeが呼び出されるようになります。ThisWorkbookモジュールのWorkbook_OpenイベントからInitAppEventsを呼び出しておけば、Excel起動時に自動で監視が始まります。
' ThisWorkbookモジュール
Private Sub Workbook_Open()
InitAppEvents
End Sub
複数のブックを個別に監視する
Applicationレベルのイベントは便利ですが、「特定の開いているブックだけを個別に監視したい」というケースもあります。その場合は、監視対象ごとにクラスのインスタンスを作成し、コレクションで管理します。
' クラスモジュール「clsWorkbookWatcher」
Public WithEvents TargetBook As Workbook
Private Sub TargetBook_BeforeClose(Cancel As Boolean)
Debug.Print TargetBook.Name & " を閉じようとしています"
End Sub
Private Sub TargetBook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Debug.Print TargetBook.Name & ":" & Sh.Name & " が変更されました"
End Sub
' 標準モジュール
Public watchers As Collection
Sub WatchAllOpenBooks()
Dim wb As Workbook
Dim watcher As clsWorkbookWatcher
Set watchers = New Collection
For Each wb In Application.Workbooks
Set watcher = New clsWorkbookWatcher
Set watcher.TargetBook = wb
watchers.Add watcher
Next wb
Debug.Print watchers.Count & "冊のブックを監視対象にしました"
End Sub
このように、開いているブックの数だけクラスのインスタンスを作成してコレクションに格納することで、ブックごとに個別のイベント処理を持たせることができます。
WithEventsを使うときの注意点
オブジェクト変数はスコープを保つ必要がある
WithEventsで宣言したオブジェクトは、参照が保持されている間だけイベントを受け取り続けます。プロシージャ内のローカル変数としてインスタンスを作成すると、プロシージャの終了と同時にオブジェクトが破棄され、イベントが発生しなくなります。上記のサンプルのように、標準モジュールのモジュールレベル変数(Public/Private)としてインスタンスを保持しておくことが重要です。
Sub WrongExample()
' ローカル変数だとプロシージャ終了時に破棄され、イベントが発火しなくなる
Dim appEvents As New clsAppEvents
Set appEvents.App = Application
End Sub
監視を止めたいときはNothingを代入する
イベントの監視を停止したい場合は、WithEventsのオブジェクトにNothingを代入します。
Sub StopWatching()
Set appEvents.App = Nothing
End Sub
イベントプロシージャは自動生成される名前を使う
イベントプロシージャの名前は「オブジェクト変数名_イベント名」の形式で固定されています。コードウィンドウ左上のオブジェクト一覧からオブジェクト変数を選択し、右上のプロシージャ一覧からイベント名を選ぶと、正しいプロシージャの雛形が自動生成されるので、手入力よりもこちらを使うのが安全です。
まとめ
WithEventsを使うと、ThisWorkbookやSheetモジュールに固定されたイベント処理では実現できない、複数のブックやアプリケーション全体を対象とした柔軟なイベント監視が可能になります。ポイントは、監視用のオブジェクトをモジュールレベル変数として保持し続けることです。
複数ブックを横断した変更履歴の記録や、アドイン開発でのイベント制御など、一歩進んだVBA開発に挑戦したい方はぜひ活用してみてください。


コメント