Visio VSDX肥大化の呪縛を断つ。未使用ステンシルを死滅させ、ファイルサイズを極限まで最適化するVBAアーキテクチャ
開発現場でよくある悪夢を語ろう。
「たった数個の図形を描き変えただけなのに、なぜかVSDXファイルの容量が数MBから数十MBに膨れ上がっている」
「図面を整理したはずなのに、古いマスターシェイプの残骸がドキュメントに居座り続けている」
Microsoft Visioの内部構造を知る者にとって、これは周知の事実だ。Visioは図面内で一度でも使用した(あるいはドラッグ&ドロップでドキュメントステンシルに取り込まれた)マスターシェイプを、図面を削除した後もゾンビのように内部に保持し続ける仕様になっている。
この「ゴミ」を放置したまま定期レポートとして保存・共有を繰り返すと、VSDX(実体はZIPパッケージ)の中身は不要なXMLとプレビュー画像で埋め尽くされ、SharePointやメールサーバーのストレージを無駄に圧迫するだけでなく、Visioの描画エンジン自体のパフォーマンス低下をも引き起こす。
今回は、業務自動化の現場で即座に採用できる、「図面内で本当ールに使用されていないマスターシェイプを完全同定し、保存前にパージ(削除)する堅牢なVBAスクリプト」を伝授しよう。
—
なぜ素朴な削除コードでは失敗するのか?(設計思想の罠)
巷に出回る「マスターシェイプを削除するコード」の多くは、実務の現場では使い物にならない。なぜなら、以下のVisio特有のオブジェクトモデルの罠を無視しているからだ。
1. 「使用中」の定義の誤り
ページ上に図形(Shape)として存在していなくとも、他のマスターシェイプのサブシェイプとして参照されている場合や、コネクタの接続情報に絡んでいる場合がある。単純な参照カウントだけでは誤爆する。
2. コレクションのインデックスシフト
`For i = 1 To Masters.Count` のようなループ内で、削除メソッドを実行するとコレクションのインデックスがズレ、`Runtime Error` や意図しないスキップが発生する。
3. ドキュメントステンシルの排他制御
アクティブなドキュメントの `Document.Masters` を操作する際、Visioの再描画イベントやUndoスコープとの競合により、オブジェクトが正解放破されないケースがある。
これらを完全にクリアし、「1バイトたりとも無駄なデータを残さない」プロダクションコードの全体像を以下に提示する。
—
プロダクションコード:未使用マスターシェイプ・パージエンジン
以下のコードをVisioの標準モジュールに貼り付けて実行してほしい。実務での誤動作を防ぐため、削除対象の判定ロジックには厳密な依存関係チェックを組み込んでいる。
Option Explicit
‘ ==============================================================================
‘ 業務自動化プロフェッショナル仕様: Visio未使用ステンシルパージエンジン
‘ 概要: アクティブドキュメントのDocumentステンシルをスキャンし、
‘ 全ページ・全マスターから参照されていない孤立マスターを完全削除する。
‘ ==============================================================================
Public Sub PurgeUnusedMasters()
Dim startTime As Double
startTime = Timer
‘ 画面描画とイベントを停止し、処理速度を極限まで引き上げる(鉄則)
Application.ScreenUpdating = False
Application.EventEnabled = False
Dim doc As Visio.Document
Set doc = ActiveDocument
‘ 読み取り専用図面の場合は即座に弾く
If doc.ReadOnly Then
MsgBox “この図面は読み取り専用です。パージ処理を中止します。”, vbCritical, “致命的エラー”
GoTo CleanUp
End If
Dim masterCountBefore As Long
masterCountBefore = doc.Masters.Count
‘ 1. 全マスターの参照フラグ(Dictionary)を初期化
‘ ※VBA標準のDictionaryを使用するため「Microsoft Scripting Runtime」を参照設定するか、
‘ 遅延バインディングを使用する(今回は堅牢性を考慮し遅延バインディングを採用)
Dim refDict As Object
Set refDict = CreateObject(“Scripting.Dictionary”)
Dim mast As Visio.Master
For Each mast In doc.Masters
‘ キー:マスター名、値:参照カウンタ(初期値 0)
refDict.Add mast.Name, 0
Next mast
‘ 2. ページ上の図形からの参照をスキャン
Dim pg As Visio.Page
For Each pg In doc.Pages
ScanShapesForMasters pg.Shapes, refDict
Next pg
‘ 3. 他のマスター内部(ネストされたシェイプ)からの参照をスキャン
For Each mast In doc.Masters
ScanShapesForMasters mast.Shapes, refDict
Next mast
‘ 4. 参照カウンタが 0 のマスターを逆順ループで安全に削除
Dim i As Long
Dim deletedCount As Long
deletedCount = 0
‘ コレクション破壊を防ぐため、後ろからループを回す
For i = doc.Masters.Count To 1 Step -1
Set mast = doc.Masters(i)
‘ ドキュメント固有の特殊マスター(ガイドやページ背景等)を除外する防御策
If Not IsSystemMaster(mast) Then
If refDict.Exists(mast.Name) Then
If refDict(mast.Name) = 0 Then
‘ デバッグ出力(イミディエイトウィンドウで確認可能)
Debug.Print “削除対象マスター: ” & mast.Name
mast.Delete
deletedCount = deletedCount + 1
End If
End If
End If
Next i
‘ 5. 結果の通知と最適化保存の促し
Application.ScreenUpdating = True
Application.EventEnabled = True
Dim endTime As Double
endTime = Timer
MsgBox “パージ処理が完了しました。” & vbCrLf & _
“———————————-” & vbCrLf & _
“削除前マスター数: ” & masterCountBefore & vbCrLf & _
“削除された孤立マスター数: ” & deletedCount & vbCrLf & _
“残存マスター数: ” & doc.Masters.Count & vbCrLf & _
“処理時間: ” & Format(endTime – startTime, “0.00秒”), _
vbInformation, “最適化完了”
Exit Sub
CleanUp:
Application.ScreenUpdating = True
Application.EventEnabled = True
End Sub
‘ ——————————————————————————
‘ 再帰的シェイプ走査プロシージャ(グループ化された図形やサブシェイプを網羅)
‘ ——————————————————————————
Private Sub ScanShapesForMasters(shapes As Visio.Shapes, ByRef dict As Object)
Dim shp As Visio.Shape
Dim subShp As Visio.Shape
Dim mastName As String
For Each shp In shapes
‘ 図形がマスターから派生している場合、そのマスター名を特定
On Error Resume Next
If Not shp.Master Is Nothing Then
mastName = shp.Master.Name
If dict.Exists(mastName) Then
dict(mastName) = dict(mastName) + 1
End If
End If
On Error GoTo 0
‘ グループ図形などの子シェイプ(Shapes)が存在する場合は再帰的に走査
If shp.Shapes.Count > 0 Then
ScanShapesForMasters shp.Shapes, dict
End If
Next shp
End Sub
‘ ——————————————————————————
‘ システム予約マスターや削除してはならない特殊マスターの判定
‘ ——————————————————————————
Private Function IsSystemMaster(mast As Visio.Master) As Boolean
‘ 必要に応じて、削除不可とする特定のマスター名や種類をフィルタリング
‘ 例: ガイド線や特定のビルトインステンシルなど
If mast.Type = visTypeGuide Then
IsSystemMaster = True
Exit Function
End If
‘ 例外的に保護したいマスター名がある場合はここでTrueを返す
‘ Select Case mast.Name
‘ Case “ImportantTemplateMaster”: IsSystemMaster = True
‘ Case Else: IsSystemMaster = False
‘ End Select
IsSystemMaster = False
End Function
—
チーフアーキテクトが教える、現場で活きる実装の急所
このコードをそのまま組み込むだけで十分な効果が得られるが、さらに大規模な業務システムや自動化バッチへ組み込む場合の「プロの知見」を共有しよう。
1. 処理前の強制バックアップとトランザクション
VSDXのマスター削除は破壊的な変更である。もし特殊なカスタムプロパティ(Propセクション)を持ったマスターが誤って削除対象外判定の漏れで消された場合、図面のメタデータが破損するリスクがゼロではない。
プログラムからこのコードを呼び出す際は、必ず事前に `ActiveDocument.SaveAs` で別名バックアップ(あるいはタイムスタンプ付きの退避)を行うアーキテクチャにすること。
2. サイレントバッチ処理への応用
このスクリプトをタスクスケジューラや外部のRPA(UiPathやPower Automateなど)から呼び出す場合、`Application.Visible = False` でVisioをバックグラウンド起動し、ポップアップメッセージ(`MsgBox`)が原因でプロセスがフリーズする事故を防ぐため、メッセージ部分をログ出力(`FileSystemObject` によるテキスト書き込み)に置き換えるカスタマイズが必須となる。
3. VSDX自体の物理圧縮(ZIP構造の理解)
VBAでマスターシェイプを削除してファイルを上書き保存しても、Visioの内部ストレージ領域(OLEストリームやXMLパッケージの一部)に断片化が残る場合がある。
究極のファイルサイズ削減を狙うなら、このVBAでパージを実行した後にファイルを一度クローズし、VSDXの拡張子を `.zip` に一時変更して内部の `visio/pages/` や `visio/masters/` のゴミXMLをデフラグするようなサーバーサイド処理(C#やPythonによるZIP再圧縮)を組み合わせると、ファイルサイズを理論値の限界まで削ぎ落とすことが可能だ。
—
結びにかえて
業務効率化とは、ただ手作業を自動化することではない。システムが抱える「目に見えない贅肉」を削ぎ落とし、常に健全で高速に稼働するインフラストラクチャを維持し続けることだ。
今回提供したパージエンジンをあなたの開発するVisioソリューションに組み込み、肥大化するファイル容量の悩みから解放された軽快なワークフローを手に入れてほしい。
