Visio VBAを掌握する極限の知見:Document_BeforeDocumentCloseによるPDF自動同期の全貌
Visioにおける最大の悪夢は何か。それは、「修正が加えられた最新のVSDXと、下流工程に共有された古いPDFの乖離」である。
設計図面やネットワーク図の運用において、エンジニアが手動でPDFを再出力し忘れるヒューマンエラーは、システム間連携やプロジェクトの遅延において致命傷となる。ファイルが閉じられるその瞬間を検知し、未保存の変更(Dirtyフラグ)を検知して、バックグラウンドで強制的にPDFを同期する――これを実現するのが、`Document_BeforeDocumentClose` イベントを軸としたイベント駆動型アーキテクチャである。
本稿では、単なるサンプルの提示にとどまらず、Visioのオブジェクトライフサイクル、イベントハンドリングの罠、そして実戦で耐えうる堅牢なVBAコードの極限を解説する。
—
1. Visioイベントモデルの深層と「BeforeDocumentClose」の罠
VisioのVBA環境において、ドキュメントレベルのイベントを捕捉するには `ThisDocument` クラスモジュールの利用が不可欠である。しかし、ここで多くの開発者がハマる致命的な罠がある。
イベントの重複発火と無限ループの回避
`Document_BeforeDocumentClose` は、ドキュメントが閉じられる直前に発火する。もしこのイベント内でPDFのエクスポートを行い、その過程でドキュメントに何らかの変更(プロパティの書き込みなど)を加えると、再び「未保存の変更」が発生し、保存確認ダイアログの挙動が狂うか、最悪の場合は無限ループに陥る。
さらに、VisioのCOMオブジェクトモデルはマルチスレッドセーフではない。バックグラウンド処理を偽装して非同期処理を行おうとすると、Visioのガベージコレクタと競合し、強制終了(クラッシュ)を引き起こす。したがって、「閉じる直前の同期は、完全な同期処理(Synchronous)として最小限のオーバーヘッドで実行し、かつ余計なDirtyフラグを立てない」のが鉄則である。
—
2. 実装コード:堅牢なPDF自動同期エンジン
以下のコードを、対象となるVisio図面(またはテンプレート)の `ThisDocument` クラスモジュールに実装せよ。
実務において耐えうるよう、ファイルパスの動的解決、エラーハンドリング、そしてオブジェクトの明示的な解放(メモリ最適化)を組み込んでいる。
Option Explicit
‘ =========================================================================
‘ クラスモジュール: ThisDocument
‘ 概要: ドキュメントクローズ前のイベントをフックし、未保存変更がある場合に
‘ 自動的に同一階層へ同名PDFを強制出力する
‘ =========================================================================
Private Sub Document_BeforeDocumentClose(ByVal Doc As IVDocument)
On Error GoTo ErrorHandler
‘ 1. システムモルフやアドインによる一時的なドキュメント閉鎖を除外
If Doc.ReadOnly = visReadOnly Then Exit Sub
If Doc.Path = “” Then Exit Sub ‘ 未保存の新規ファイルは対象外
‘ 2. 変更フラグ(Dirty)の評価
‘ ユーザーが明示的に保存した直後であっても、ワークフロー上
‘ 「最終版としてのPDF化」を担保したい場合は条件を適宜調整すること。
If Doc.Saved = False Then
Dim userChoice As VbMsgBoxResult
userChoice = MsgBox(“図面に未保存の変更があります。” & vbCrLf & _
“最新の状態をPDFに同期してから閉じますか?”, _
vbYesNo + vbExclamation, “Visio自動PDF同期システム”)
If userChoice = vbYes Then
‘ 3. PDFファイルパスの動的生成(VSDXと同一ディレクトリ・同名)
Dim targetPdfPath As String
targetPdfPath = GetTargetPdfPath(Doc.FullName)
‘ 4. エクスポート実行(VisioのネイティブExportメソッドを使用)
‘ ※注: 巨大な図面の場合、ExportメソッドはUIスレッドをブロックするため
‘ イベント内で完結させる必要がある。
Doc.Export targetPdfPath
‘ 5. 必要に応じてドキュメント自体を上書き保存する
Doc.Save
MsgBox “PDFの同期が完了しました:” & vbCrLf & targetPdfPath, _
vbInformation, “同期完了”
End If
End If
ExitSub:
Exit Sub
ErrorHandler:
‘ 異常終了時もVisio自体のクローズプロセスを阻害しないよう、エラーをログ出力して安全に抜ける
MsgBox “PDFの自動同期中に致命的なエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“説明: ” & Err.Description, vbCritical, “Critical Error”
Resume ExitSub
End Sub
‘ =========================================================================
‘ 補助関数: フルパスから拡張子を置換してPDFパスを生成する
‘ =========================================================================
Private Function GetTargetPdfPath(ByVal vsdxPath As String) As String
Dim dotPos As Long
dotPos = InStrRev(vsdxPath, “.”)
If dotPos > 0 Then
GetTargetPdfPath = Left(vsdxPath, dotPos – 1) & “.pdf”
Else
GetTargetPdfPath = vsdxPath & “.pdf”
End If
End Function
—
3. チーフアーキテクトが解説するコードの急所
A. オブジェクトのライフサイクルとメモリ管理
今回のコードでは `Doc` 引数(`IVDocument`)を直接参照しており、新たなドキュメントインスタンスの生成(`CreateObject` や `Application.Documents.Open` 等)を行っていない。
VBAにおけるCOMオブジェクトのリークの大部分は、不要な参照の保持や、解放忘れ(`Set obj = Nothing` の欠落)に起因する。イベントプロシージャ内で渡される暗黙のオブジェクト参照は、イベント終了時に自動的にスコープ外となるため安全だが、もし追加でシェイプやページを操作するコードを挿入する場合は、必ずローカル変数を `Nothing` で解放する規しを徹底してほしい。
B. エラーハンドリングの極意
`On Error GoTo ErrorHandler` は必須である。もし `Doc.Export` の実行中に、出力先のPDFが別プロセス(Adobe AcrobatやPDF閲覧ソフト)でロックされていた場合、VBAは実行時エラー(権限エラー等)を投げる。
この時、エラーハンドラーを実装していないと、Visioの終了シーケンスそのものがクラッシュし、ユーザーが編集していたVSDXファイルの変更内容が吹き飛ぶという最悪の事態を招く。エラーが発生したとしても、「図面ファイルのクローズ処理を妨げないこと」がシステム設計上の最優先事項である。
C. レガシー環境・エンタープライズ要件への拡張
社内システムやSharePoint/OneDriveなどの同期フォルダーと連携する場合、パスに日本語が含まれることや、同期競合によるファイルロックが発生しやすい。
より堅牢なシステムを目指すのであれば、FileSystemObject (FSO) を用いて出力先フォルダの書き込み権限を事前にチェックするバリデーション層を `GetTargetPdfPath` の前段に挟むべきである。
—
総括
VBAを「おまけのマクロ」と侮るなかれ。イベント駆動型のアーキテクチャを正しく理解し、Visioのオブジェクトモデルの制約を熟知していれば、人間の手作業に依存しない鉄壁のドキュメント管理基盤を構築できる。
規律あるコードのみが、システムを破綻から守る。次の開発フェーズにおいて、この知見が貴殿の武器となることを確信している。
