マクロ記録の先へ。Visio VBAで「監査証跡(Audit Trail)」を構築する――イベントハンドリングの極意
「マクロの記録」ボタンを押して、生成されたコードを眺めて「ふーん、こんな感じか」と納得した。そこが、あなたのVisio VBAエンジニアとしての本当のスタートラインです。
こんにちは。今日は、単なる「作業の自動化」の一歩先、「システムの自動監視」という領域へ皆さんを誘いたいと思います。
今回扱うテーマは、「ページが追加・削除された瞬間に、その履歴を自動的にテキストファイルへ書き出す」という仕組みです。これは、業務プロセスが厳格に管理される現場では「監査証跡(Audit Trail)」と呼ばれる、極めて重要な技術です。
「誰が、いつ、何をしたか」をシステムが勝手に記録してくれる。そんな、まるで熟練の守護神のようなプログラムを一緒に作っていきましょう。
1. なぜ「マクロの記録」では足りないのか?
多くの初学者が陥る壁があります。それは、「アクション(動作)を自動化することはできても、イベント(出来事)を検知することはできない」という問題です。
- マクロの記録: 「ボタンを押したら、ページを作る」という命令。
- イベントハンドリング: 「(ユーザーが何をしたかに関わらず)ページが作られたら、記録する」という反応。
今回のゴールは後者です。ユーザーが手動でページを追加しようが、別のマクロで追加しようが、その「瞬間」を逃さずにキャッチする。これこそが、プロフェッショナルな自動化エンジニアが持つべき視点です。
2. アーキテクチャの設計図:クラスモジュールという「センサー」
Visio VBAでイベントを監視するには、通常のモジュールではなく「クラスモジュール」という特別な場所を使います。
イメージしてください。
通常のモジュールは「指示書」です。一方、クラスモジュールは、特定の出来事を待ち構える「センサー(監視装置)」です。
今回の設計図はこうなります:
1. クラスモジュール (`clsEventWatcher`): Visioのドキュメントを監視し、「ページが追加された!」という信号を受け取ったら、ログ出力関数を呼び出す「センサー」の役割。
2. 標準モジュール: センサーを起動(インスタンス化)し、監視を開始させる「スイッチ」の役割。
3. ログファイル: 記録が書き込まれる「帳簿」の役割。
3. 実装:魂を込めたコード
それでは、実際にコードを書いていきましょう。手順に従って進めてください。
ステップ1:クラスモジュールの作成
まず、VBAエディタのメニューから `挿入` -> `クラスモジュール` を選択してください。
プロパティウィンドウで、オブジェクト名を `clsEventWatcher` に変更してください。ここが運命の分かれ道です。名前を間違えると、センサーは反応しません。
' ============================================================
' クラス名: clsEventWatcher
' 役割: Documentのイベントを監視し、ログ出力のトリガーとなる
' ============================================================
Option Explicit
' WithEvents を使うことで、特定のオブジェクトのイベントを「待ち受け」状態にします
Private WithEvents objDoc As Visio.Document
' 監視を開始するためのメソッド
Public Sub Initialize(ByRef targetDoc As Visio.Document)
Set objDoc = targetDoc
Debug.Print "--- 監視システム、起動完了 ---"
End Sub
' --- イベント: ページが新規追加されたとき ---
Private Sub objDoc_NewPage(ByVal Page As Visio.Page)
Call WriteAuditLog("PAGE_ADD", "新しいページが追加されました: " & Page.Name)
End Sub
' --- イベント: ページが削除されたとき ---
Private Sub objDoc_DeletePage(ByVal Page As Visio.Page)
' 注意: 削除イベントでは、既にページが存在しない場合があるため
' 慎重なハンドリングが必要ですが、ここでは基本形を示します
Call WriteAuditLog("PAGE_DELETE", "ページが削除されました")
End Sub
' --- ログ出力用プライベート関数 ---
' 実際の現場では、ここをファイル出力やデータベース書き込みに拡張します
Private Sub WriteAuditLog(ByVal actionType As String, ByVal message As String)
Dim logPath As String
Dim fileNum As Integer
Dim logEntry As String
' ログファイルの保存先(このExcelファイルと同じフォルダに audit_log.txt を作成)
logPath = ThisDocument.Path & "\audit_log.txt"
' ログのフォーマット: [日時] [アクション] メッセージ
logEntry = "[" & Now & "] [" & actionType & "] " & message
' デバッグ出力(イミディエイトウィンドウに表示)
Debug.Print logEntry
' ファイルへの追記処理
On Error Resume Next ' ファイルが開けない場合などのエラー回避
fileNum = FreeFile
Open logPath For Append As #fileNum
Print #fileNum, logEntry
Close #fileNum
On Error GoTo 0
End Sub
ステップ2:標準モジュールの作成
次に、`挿入` -> `標準モジュール` を選択します。ここが、センサーを動かすための「スイッチ」になります。
' ============================================================
' モジュール名: modMain
' 役割: 監視システムの起動と停止を管理する
' ============================================================
Option Explicit
' クラスのインスタンスを保持するための変数
' 【超重要】ここをモジュールレベル変数として宣言しないと、
' プロシージャが終わった瞬間にセンサーが「寿命」を迎えて消滅してしまいます!
Private Watcher As clsEventWatcher
' 監視を開始するマクロ
Sub StartAuditSystem()
' センサー(クラス)のインスタンスを作成
Set Watcher = New clsEventWatcher
' 現在のドキュメントをセンサーにセット
Watcher.Initialize ThisDocument
MsgBox "監査ログの記録を開始しました。" & vbCrLf & _
"ログファイル: " & ThisDocument.Path & "\audit_log.txt", vbInformation
End Sub
' 監視を停止するマクロ
Sub StopAuditSystem()
' インスタンスを破棄して、メモリから解放する
Set Watcher = Nothing
MsgBox "監査ログの記録を停止しました。", vbExclamation
End Sub
4. エンジニアが教える「ここをクリアすればバッチリ」な注意点
コードをコピペして動かすだけでは、あなたはまだ「ユーザー」です。エンジニアとして、以下の2つの落とし穴を必ず理解しておいてください。
① オブジェクトのライフサイクル(生存期間)
標準モジュールで `Dim Watcher As clsEventWatcher` を、`StartAuditSystem` というプロシージャの中で宣言してしまったらどうなるでしょうか?
' 失敗例:これでは動かない!
Sub StartAuditSystem()
Dim Watcher As clsEventWatcher ' プロシージャ内のローカル変数
Set Watcher = New clsEventWatcher
Watcher.Initialize ThisDocument
End Sub ' <--- ここで Watcher はメモリから消滅し、監視も止まる!
プロシージャが終了した瞬間に、変数 `Watcher` は消えてしまいます。監視を継続させるには、必ずモジュールレベル(プロシージャの外側)で宣言してください。これは、オブジェクト指向プログラミングにおける「スコープ(有効範囲)」の極めて重要な概念です。
② エラーハンドリングの重み
ログ出力の際、もし「ログファイルが別のプログラムで開かれていたら?」あるいは「書き込み権限がないフォルダだったら?」プログラムはエラーで止まってしまいます。
業務自動化において、「監視プログラム自体がエラーで落ちる」ことは、本末転倒な最悪の事態です。
上記のコードでは `On Error Resume Next` を使って、ログが書けなくてもメインのVisio操作を妨げないように配慮しています。
5. まとめ:君のコードに「意志」を持たせよう
今回の実装で、あなたは単なる「作業の自動化」を超え、「システムの挙動をコントロールする」という一段上のレイヤーに足を踏み入れました。
1. クラスモジュールを使って、イベントを待ち受ける「センサー」を作った。
2. WithEvents を使って、Visioの内部イベントにフックした。
3. オブジェクトの寿命(スコープ)を意識して、監視を継続させた。
ここをマスターすれば、Visio VBAの基本はバッチリです。次は、ページの内容が変わったとき、あるいは図形の色が変わったとき……そんな「変化」を捉える旅に出かけましょう。
さあ、次はどんな自動化の世界を創りますか?
