【ExcelVBA・マクロ】WithEventsの使い方|クラスモジュールで複数ブックのイベントを検知する方法【コピペOK】

ExcelVBA

「開いているすべてのブックでシートが変更されたら記録したい」「Workbook_OpenやWorksheet_Changeのようなイベントを、あとから追加したブックにも仕込みたい」——こうした要望は、ThisWorkbookモジュールやSheetモジュールに直接コードを書くだけでは実現できません。そんなときに使うのがWithEventsキーワードです。

この記事では、クラスモジュールとWithEventsを使ってイベントを検知する基本の仕組みから、複数ブックのイベントをまとめて監視する実践的なコード例、つまずきやすい注意点まで解説します。

スポンサーリンク
スポンサーリンク

WithEventsとは何か

通常、Workbook_OpenWorksheet_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開発に挑戦したい方はぜひ活用してみてください。

スポンサーリンク
スポンサーリンク
ExcelVBA
シェアする
いがぴをフォローする

コメント

タイトルとURLをコピーしました