【Masterclass】Visio VBAにおける「監査ログシステム」の構築:Classモジュールを用いた堅牢なイベントハンドリング設計
自動化ツールを「単なる便利なスクリプト」で終わらせるか、それとも「エンタープライズ品質のシステム」へと昇華させるか。その境界線は、「予期せぬ変更に対する追跡可能性(Traceability)」を設計に組み込んでいるかどうかにあります。
現場で大規模な図面管理を行うツールを運用していると、必ずと言っていいほど「いつ、誰が、どのページを削除したのか分からない」という事態に直面します。標準モジュールに羅列された、スパゲッティのようなコードでは、このような動的なイベントを捕捉することは不可能です。
本記事では、Visio VBAにおけるClassモジュールを用いたイベント監視(Observerパターン)を駆使し、ページの追加・削除をリアルタイムで検知してログに刻む、プロフェッショナル仕様の監査ログシステムの構築手法を伝授します。
1. なぜ標準モジュールでは「不十分」なのか
多くの開発者は、ボタンを押した時に動く処理(マクロ)を書くことには長けています。しかし、「ユーザーが手動で行った操作」を監視しようとした瞬間、壁にぶつかります。
標準モジュールのプロシージャは、呼び出された瞬間に実行され、終われば消滅します。これに対し、ページの追加や削除といった「アプリケーションの状態変化」を捉えるには、アプリケーションのライフサイクルと同期して動き続ける「常駐型のリスナー」が必要です。
ここで登場するのがClassモジュールです。Classモジュール内で `WithEvents` キーワードを使用することで、Visioアプリケーションのイベントを、オブジェクトのインスタンスが存在する限り、継続的にフックすることが可能になります。
2. 設計思想:責務の分離(Separation of Concerns)
堅牢なシステムを構築するためには、コードを以下の3つのレイヤーに分離して設計します。
1. Event Observer (Class Module): Visioのイベントを監視し、変化を検知する「目」の役割。
2. Logging Engine (Standard Module): ログのフォーマットを整え、ファイルやDBへ書き出す「手」の役割。
3. Application Controller (Standard Module): Observerを起動し、ライフサイクルを管理する「脳」の役割。
この分離により、例えば「ログの出力先をテキストファイルからSQL Serverに変更したい」といった要件変更が発生した際も、Observer側のコードを一切汚さずに修正が可能になります。
3. 実装:プロダクショングレードのコード
それでは、実際に動くコードを実装していきましょう。
Step 1: Classモジュールの作成
まず、クラスモジュールを挿入し、名前を `clsVisioObserver` としてください。
' Class Module: clsVisioObserver
Option Explicit
' Visio Applicationのイベントをフックするための宣言
' WithEventsを使用することで、このクラスのインスタンスが生きている間、イベントを捕捉し続ける
Private WithEvents App As Visio.Application
' Observerの初期化
Public Sub Initialize(ByRef visioApp As Visio.Application)
Set App = visioApp
End Sub
' ページが追加された時のイベントハンドラ
Private Sub App_PageAdd(ByVal Page As Visio.Page)
' ログエンジンへ処理を委譲(責務の分離)
modLogger.WriteLog "PAGE_ADD", "Page Name: " & Page.Name
End Sub
' ページが削除された時のイベントハンドラ
Private Sub App_PageDelete(ByVal Page As Visio.Page)
' 削除されたページ名を取得(削除直後のため、オブジェクトの有効性に注意が必要な場合がある)
' ここではPageオブジェクトが渡される仕様に基づき記録
modLogger.WriteLog "PAGE_DELETE", "Page Name: " & Page.Name
End Sub
' クラスの破棄処理
Private Sub Class_Terminate()
Set App = Nothing
End Sub
Step 2: 標準モジュールの作成
次に、ログ出力ロジックと制御ロジックを記述する標準モジュール(`modMain` および `modLogger`)を作成します。
' Standard Module: modMain
Option Explicit
' Observerのインスタンスを保持するためのグローバル変数
' これが消滅すると、イベントの監視も止まってしまう(重要!)
Private mObserver As clsVisioObserver
''' <summary>
''' 監査ログシステムの起動
''' </summary>
Public Sub StartAuditSystem()
If mObserver Is Nothing Then
Set mObserver = New clsVisioObserver
mObserver.Initialize Visio.Application
MsgBox "監査ログシステムが起動しました。", vbInformation
Else
MsgBox "システムは既に稼働中です。", vbExclamation
End If
End Sub
''' <summary>
''' 監査ログシステムの停止
''' </summary>
Public Sub StopAuditSystem()
Set mObserver = Nothing
MsgBox "監査ログシステムを停止しました。", vbInformation
End Sub
' ---
' Standard Module: modLogger
Option Explicit
''' <summary>
''' ログをファイルへ追記するエンジン
''' </summary>
''' <param name="action">操作種別 (PAGE_ADD, PAGE_DELETE等)</param>
''' <param name="details">詳細情報</param>
Public Sub WriteLog(ByVal action As String, ByVal details As String)
On Error GoTo ErrorHandler
Dim logFile As String
Dim fileNo As Integer
Dim userName As String
' ログの保存先(実行ファイルと同じディレクトリを想定)
logFile = ThisWorkbook.Path & "\audit_log.txt"
fileNo = FreeFile
' 実行ユーザーと時刻の取得
userName = Environ("USERNAME")
' ファイルへの追記書き込み
' Openステートメントは、ファイルが存在しない場合は自動作成する
Open logFile For Append As #fileNo
Print #fileNo, Format(Now, "yyyy-mm-dd HH:mm:ss") & " | " & _
"USER: " & Left(userName & Space(20), 20) & " | " & _
"ACTION: " & Left(action & Space(10), 10) & " | " & _
"DETAILS: " & details
Close #fileNo
Exit Sub
ErrorHandler:
' 本来はここでイベントログへの書き出しや、管理者への通知を行う
' ログ書き込み失敗がメイン処理を止めてはならない(致命的なエラーを避ける)
Debug.Print "Log Error: " & Err.Description
End Sub
4. プロフェッショナルが直面する「罠」と回避策
このコードをそのままコピペしても動きますが、真のエンジニアは以下の3点に留意します。
① インスタンスの生存期間(Scope Management)
`mObserver` 変数を `Public` ではなく `Private`(モジュールレベル)にし、標準モジュールのトップレベルで宣言している点に注目してください。もしこれを `Sub` の中で宣言してしまうと、そのプロシージャが終了した瞬間にオブジェクトが破棄され、イベント監視が即座に終了します。「変数のスコープがライフサイクルを決める」、これはVBAにおける鉄則です。
② I/Oのオーバーヘッドと競合
上記の例では `Open … For Append` を使用しています。これはシンプルですが、ネットワークドライブ上のファイルに対して、複数のユーザーが同時に書き込もうとすると “Permission Denied” エラーが発生します。
- 実務への適用: 大規模環境では、直接ファイルに書くのではなく、一度共有の「ログ収集用Excel」や「SQLite」、「SQL Server」へ投げ込む設計にするか、エラー時にリトライするロジックを組み込むべきです。
③ ユーザー情報の信頼性
`Environ(“USERNAME”)` は非常に便利ですが、環境変数操作によって偽装が可能です。高度なセキュリティが求められるプロジェクトでは、Windows API(`GetUserName`)を使用して、OSレベルから確実にユーザー名を特定する実装を検討してください。
5. まとめ:エンジニアリングとしての自動化
単に「ページが増えたら通知する」だけのツールを作るのは、単なるスクリプターです。
「誰が、いつ、何をしたか」を、メインのロジックを汚さずに、かつ、アプリケーションのライフサイクルに同期させて、堅牢に記録する。これこそが、アーキテクトに求められる設計思想です。
この監査ログシステムを基盤として、あなたはさらに高度な「変更履歴管理ツール」へと進化させることができるでしょう。設計の美しさは、コードの量ではなく、その構造の堅牢さに宿るのです。
