Visio VBAを掌握する極限の知見:Document.Savedプロパティの精密制御による「無駄な上書き」の根絶
エンタープライズ環境において、何千枚もの図面をバッチ処理で巡回・検証する自動化スクリプトを構築したとしよう。処理の最後に `ActiveDocument.Save` を機械的に実行する設計は、アマチュアの所業に過ぎない。
実質的なデータ変更が起きていないにもかかわらずタイムスタンプを更新し、Gitなどのバージョン管理システムやファイルサーバーの差分管理を汚染する。あるいは、ユーザーが意図しない変更によって不必要な保存確認ダイアログをポップアップさせ、バッチ処理を完全停止させる——これらは現場のエンジニアリングにおいて「悪」である。
今回は、Visioのオブジェクトモデルの深層に位置する `Document.Saved` プロパティを極限までハックし、「真に変更があった場合のみ、ダイアログを出さずにスマートに永続化する」ための実践的アーキテクチャを解説する。
—
1. Visioオブジェクトモデルにおける「状態管理」の真実
多くの開発者は、`Document.Saved` を単なる「保存されているかどうかの真偽値フラグ(True / False)」だと勘違いしている。これが、バッチ処理や大規模な図面自動生成におけるバグの温床となる。
Document.Saved の本質
`Document.Saved` は、読み取り専用のステータスではない。書き込み可能なトリガーである。
Visioの内部エンジン(`Visio.Application`)は、シェイプの追加、プロパティ(ShapeData)の書き換え、レイヤーの変更などが発生した瞬間に、内部のダーティフラグ(Dirty Flag)を `False` から `True` へと強制遷移させる。
しかし、プログラム側からこれを制御する上で、以下の致命的な罠が存在する。
1. 暗黙的な変更の発生
ビューの拡大率(Zoom)の変更や、選択状態(Selection)の変更だけでも、環境によってはドキュメントの状態が「変更あり」とみなされるケースがある。
2. Undoスタックとの同期ズレ
VBAからシェイプを生成・変形させると、Undo(元に戻す)キューが蓄積される。これを適切にハンドリングしないと、`Saved = True` に偽装しても、ドキュメントを閉じる際にVisioが独自の判定で保存ダイアログを割り込ませてくる。
—
2. 【極限コード】無駄な上書きを防ぐスマート・クローズパターン
以下のコードは、単なるプロパティの判定にとどまらず、「実質的な変更の検知」「Undoコンテキストの管理」「異常系におけるメモリ最適化」を網羅した、プロダクション品質のVBAモジュールである。
Option Explicit
‘ ==============================================================================
‘ 模块名: clsVisioDocumentGuard
‘ 概要: Document.Savedを精密制御し、無駄な上書きとダイアログ介入を根絶する
‘ ==============================================================================
Public Sub ExecuteSmartProcessAndClose()
Dim vsoApp As Visio.Application
Dim vsoDoc As Visio.Document
Dim initialSavedState As Boolean
‘ 1. アプリケーションインスタンスの取得(GetObjectフォールバック付き)
On Error Resume Next
Set vsoApp = Visio.Application
On Error GoTo 0
If vsoApp Is Nothing Then
MsgBox “Visioが起動していません。”, vbCritical, “致命的エラー”
Exit Sub
End If
Set vsoDoc = vsoApp.ActiveDocument
If vsoDoc Is Nothing Then
MsgBox “アクティブなドキュメントが存在しません。”, vbExclamation, “警告”
Exit Sub
End If
‘ 2. 処理前のSaved状態をスナップショット保存
initialSavedState = vsoDoc.Saved
‘ 画面描画とイベントの凍結(圧倒的なパフォーマンス向上とゴースト描画の防止)
vsoApp.ScreenUpdating = False
vsoApp.EventsEnabled = False
‘ 独自のUndoスコープを開始(Visioのトランザクション管理)
Dim lScopeID As Long
lScopeID = vsoApp.BeginUndoScope(“自動一括処理バッチ”)
On Error GoTo ErrorHandler
‘ ==========================================================================
‘ 【ビジネスロジック領域】ここに実際のシェイプ操作やデータ連携を記述
‘ ==========================================================================
Call PerformHeavyAxiomOperations(vsoDoc)
‘ ==========================================================================
‘ 正常終了:Undoスコープを確定
vsoApp.EndUndoScope lScopeID, True
‘ 3. Document.Savedの評価とスマート・セーブ
If vsoDoc.Saved Then
‘ エンジン側が「変更なし」と判定した場合、完全ノーコストで閉じる
Debug.Print “[Info] 変更は検知されませんでした。上書きをスキップします。”
Else
‘ 実質的な変更がある場合のみ、確認ダイアログなしでサイレント保存
‘ ※要件に応じて、ここで強制上書きか別名保存かを分岐させる
vsoDoc.Save
Debug.Print “[Info] 変更を検知しました。ドキュメントを上書き保存しました。”
End If
‘ 4. クリーンアップしてドキュメントを閉じる
‘ 既にSaved=True(または保存完了)のため、ダイアログは一切出ない
vsoDoc.Close
GoTo Finally
ErrorHandler:
‘ 異常終了:Undoスコープをロールバックし、変更を破棄
vsoApp.EndUndoScope lScopeID, False
‘ 異常時は元のSaved状態を強制復元し、ファイル破損や中途半端な保存を防ぐ
vsoDoc.Saved = initialSavedState
MsgBox “自動処理中にエラーが発生しました。変更はロールバックされます。” & vbCrLf & _
“エラー詳細: ” & Err.Description, vbCritical, “実行時エラー”
If Not vsoDoc Is Nothing Then
‘ 変更を破棄して強制閉鎖する場合の安全策
vsoDoc.Close
End If
Finally:
‘ 5. リソースの明示的解放と環境復元(メモリリークの完全防止)
vsoApp.ScreenUpdating = True
vsoApp.EventsEnabled = True
Set vsoDoc = Nothing
Set vsoApp = Nothing
End Sub
Private Sub PerformHeavyAxiomOperations(ByRef doc As Visio.Document)
‘ サンプルとしてのダミー処理:実際の業務ロジックをここに配置
Dim pag As Visio.Page
Set pag = doc.Pages(1)
‘ 例:特定の条件を満たす場合のみシェイプを追加(真に変更が必要なケース)
‘ Dim shp As Visio.Shape
‘ Set shp = pag.DrawRectangle(1, 1, 2, 2)
End Sub
—
3. シニアエンジニアが押さえるべきアーキテクチャの急所
上記のコードが、単なる「動くVBA」と一線を画す所以である3つの技術的要件を解説する。
① `BeginUndoScope` との密結合
Visio VBAにおいて、コードから図形を操作すると、自動的にUndoスタックが積まれる。これが原因で、処理が失敗した際や、実際には何も書き換えなかったにもかかわらず `Saved = False` に化ける現象が起きる。
トランザクション境界を `BeginUndoScope` で明示し、エラー時には `EndUndoScope lScopeID, False` で完全に状態を巻き戻すことで、ドキュメントの整合性を担保する。
② `ScreenUpdating` と `EventsEnabled` の二重封鎖
大規模な図面(数千のシェイプを持つP&IDやネットワーク図)を扱う場合、シェイプを1つ変更するたびにVisioのGUI描画エンジンとイベントハンドラが稼働し、`Saved` フラグの評価やメモリ消費に悪影響を及ぼす。
処理の冒頭でこれらを `False` に鎮圧し、処理の終端(`Finally`ブロック)で確実に復元する。この厳格なtry-finally的構造化が、レガシーVBA環境でのメモリリークを防ぐ唯一の盾となる。
③ 意図的なオブジェクト参照の断ち切り (`Set = Nothing`)
VBAのガベージコレクションは参照カウント方式(Reference Counting)に依存している。ローカルスコープを抜けても、COMオブジェクトの参照が残存すると、Visioプロセスがバックグラウンドにゾンビとして居座り続ける。
モジュールの出口で `Set vsoDoc = Nothing` および `Set vsoApp = Nothing` を明示的に実行し、COMコンポーネントの解放を確約すること。
—
4. チーフアーキテクトからの提言
「とりあえず動けばいい」という妥協の産物は、社内ニッチなシステムにおいて数年後に必ず爆発する。何百枚もの図面を一括処理する基幹システムの一部であればなおさらだ。
`Document.Saved` プロパティの挙動を完全に手なずけ、無駄なI/Oとユーザーへの無用なストレスを排除すること。それこそが、レガシー技術を極限まで洗練させ、真に信頼しうるエンタープライズ・オートメーションを構築するための極意である。
