Visio VBAを掌握する極限の知見:複数ドキュメント間の高度なShape同期・リンク設計
こんにちは。チーフアーキテクトの私だ。
これまで数々の巨大なプラント図面、ネットワークトポロジ、そして全社規模のプロセスフローを管理するVisio自動化システムを構築してきた。
その中で、開発者が最も深い絶望を味わうポイントがどこか知っているか?
それは「複数のVisioドキュメント(ウィンドウ)間をまたいだデータの同期・集約」だ。
「単に図形をコピペするだけなら `Copy` と `Paste` でいいだろう」と思ったそこのあなた。甘い。実務の現場でそれをやれば、メモリリーク、ゾンビプロセスの発生、マスター不整合によるデータ破損、そして何より「どのドキュメントをアクティブにしているか依存の不安定なバグ(コンテキスト依存の爆弾)」が確実にあなたを襲う。
今回は、複数ドキュメント間をVBAで完全に制御し、マスター図面から各拠点図面へShapeとそのデータを堅牢に同期・リンクさせるための「極限の知見」を授けよう。
—
1. なぜ「単なるコピペ」では実務で破綻するのか?
初心者が書くマルチドキュメント処理のアンチパターンはこうだ。
‘ 【絶対に行ってはならないアンチパターンの例】
Workbooks.Open “Master.vsdm”
ActiveDocument.Pages(1).Shapes(1).Copy
Windows(“Branch.vsdm”).Activate
ActivePage.Paste
なぜこれがプロの現場でタブー視されるのか?理由は3つある。
1. ActiveWindow / ActivePageへの過度な依存: ユーザーがマクロ実行中に別ウィンドウをクリックしただけで、対象が変わり処理が崩壊する。
2. クリップボードの汚染と低速化: `Copy`/`Paste` はWindowsのクリップボードを介するため、処理が遅く、他のアプリケーションのコピー履歴まで破壊する。
3. ShapeIDの喪失: `Paste` を行うと、新しいShapeIDが発番されるため、元のマスター図形との「親子関係(リンク)」が完全に切れる。
真のエンジニアは、クリップボードを使わず、ドキュメント間をオブジェクト参照(Object Reference)で直接結び、「Drop(ドロップ)」または「Spatial/ID Mapping(IDマッピング)」によって同期をとる。
—
2. 堅牢なマルチドキュメント連携のアーキテクチャ設計
複数の `.vsd` / `.vsdm` ファイルを安全に操作するための鉄則は以下の通りだ。
- オブジェクト変数のスコープを厳格に定義する: `Application` から `Document`, `Page`, `Shape` まで、すべてを明示的な変数に格納し、`Active…` は一切使わない。
- マスター・スレーブ構造の明確化: データの源泉(Source Document)と、配下の同期先(Target Document)をコード上で明確に分離する。
- ShapeData(カスタムプロパティ)のキーによる一致: 単なる見た目ではなく、メタデータ(例: `Prop.AssetID` など)をキーにして、どの図形を更新すべきかを判定する。
—
3. 【プロダクションコード】マスターから各拠点図面へShapeデータを同期・リンクする実用VBA
以下のコードは、開いているマスター図面から指定したマスターシェイプを取得し、ターゲットとなる拠点図面へ「存在しなければ生成、存在すればプロパティを同期」する、実務レベルの堅牢なプロシージャだ。
コピペしてそのままプロジェクトに組み込めるよう、エラーハンドリングとオブジェクトの解放処理(ガベージ対策)も完璧に組み込んでいる。
Option Explicit
Public Sub SyncShapesAcrossDocuments()
‘—————————————————————-
‘ 複数ドキュメント間 Shape同期・リンク処理
‘ 前提条件: マスター図面とターゲット図面がすでにVisioで開かれていること
‘—————————————————————-
Dim appVisio As Visio.Application
Set appVisio = Visio.Application
‘ 画面描画とアラートを停止し、処理速度を極限まで高める(プロの基本)
appVisio.ScreenUpdating = False
appVisio.EventsEnabled = False
appVisio.AlertsEnabled = False
On Error GoTo ErrorHandler
Dim srcDoc As Visio.Document
Dim tgtDoc As Visio.Document
‘ 【重要】ファイル名やタイトルで明示的にドキュメントを捕捉する(ActiveDocumentは使わない)
Set srcDoc = GetDocumentByTitle(appVisio, “Master_Core.vsdm”)
Set tgtDoc = GetDocumentByTitle(appVisio, “Branch_Tokyo.vsdm”)
If srcDoc Is Nothing Or tgtDoc Is Nothing Then
MsgBox “指定されたマスターまたはターゲットドキュメントが見つかりません。”, vbCritical
GoTo Finally
End If
Dim srcPage As Visio.Page
Dim tgtPage As Visio.Page
Set srcPage = srcDoc.Pages(1)
Set tgtPage = tgtDoc.Pages(1)
‘ マスター側の基準シェイプ(例として最初のシェイプを使用)を特定
Dim srcShape As Visio.Shape
For Each srcShape In srcPage.Shapes
‘ 特定のカスタムプロパティ(例: “Prop.DeviceID”)を持っているシェイプのみを対象とする
If srcShape.CellExists(“Prop.DeviceID”, visExistsLocally) Then
Dim deviceID As String
deviceID = srcShape.Cells(“Prop.DeviceID”).FormulaU
‘ ターゲット側に同名のDeviceIDを持つシェイプが存在するか探索
Dim tgtShape As Visio.Shape
Set tgtShape = FindShapeByProperty(tgtPage, “Prop.DeviceID”, deviceID)
If tgtShape Is Nothing Then
‘ — 【新規作成フェーズ】存在しない場合はマスターからドロップ —
‘ クリップボードを使わず、マスターシェイプのマスター(または図形自体)をDropする
Set tgtShape = tgtPage.Drop(srcShape, 2.0, 2.0) ‘ 座標(2,2)に配置
‘ プロパティの初期同期
CopyShapeData srcShape, tgtShape
Debug.Print “新規作成・同期: ” & deviceID
Else
‘ — 【更新フェーズ】既存の場合はプロパティとスタイルを同期 —
CopyShapeData srcShape, tgtShape
‘ 位置やサイズも同期させたい場合はここで制御
‘ tgtShape.Cells(“PinX”).FormulaU = srcShape.Cells(“PinX”).FormulaU
Debug.Print “データ更新・同期: ” & deviceID
End If
End If
Next srcShape
MsgBox “ドキュメント間の同期が正常に完了しました。”, vbInformation
Finally:
‘ 描画とイベントの復旧(必ず実行させること)
appVisio.ScreenUpdating = True
appVisio.EventsEnabled = True
appVisio.AlertsEnabled = True
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical
Resume Finally
End Sub
‘—————————————————————-
‘ 補助関数: ドキュメント名(タイトル)からDocumentオブジェクトを取得
‘—————————————————————-
Private Function GetDocumentByTitle(app As Visio.Application, docName As String) As Visio.Document
Dim doc As Visio.Document
For Each doc In app.Documents
If InStr(doc.Name, docName) > 0 Then
Set GetDocumentByTitle = doc
Exit Function
End If
Next doc
Set GetDocumentByTitle = Nothing
End Function
‘—————————————————————-
‘ 補助関数: ページ内から指定プロパティの値が一致するシェイプを検索
‘—————————————————————-
Private Function FindShapeByProperty(page As Visio.Page, propName As String, propValue As String) As Visio.Shape
Dim shp As Visio.Shape
For Each shp In page.Shapes
If shp.CellExists(propName, visExistsLocally) Then
If shp.Cells(propName).FormulaU = propValue Then
Set FindShapeByProperty = shp
Exit Function
End If
End If
Next shp
Set FindShapeByProperty = Nothing
End Function
‘—————————————————————-
‘ 補助プロシージャ: ShapeData(カスタムプロパティ)の値を転写する
‘—————————————————————-
Private Sub CopyShapeData(srcShp As Visio.Shape, tgtShp As Visio.Shape)
‘ 実務では必要なセルの数だけループするか、特定のPropセクションを同期させる
‘ ここでは簡略化のため、Prop.DeviceIDとProp.Statusを同期
On Error Resume Next
If srcShp.CellExists(“Prop.Status”, visExistsLocally) And tgtShp.CellExists(“Prop.Status”, visExistsLocally) Then
tgtShp.Cells(“Prop.Status”).FormulaU = srcShp.Cells(“Prop.Status”).FormulaU
End If
On Error GoTo 0
End Sub
—
4. チーフアーキテクトからの実務アドバイス:パフォーマンスと保守の極意
この規模の自動化を現場に導入する際、以下の3点に直面するはずだ。これらを事前に潰しておくことが、真に「保守性の高いプロダクションコード」への分かれ道となる。
1. 画面描画の抑制 (`ScreenUpdating = False`) は絶対の義務
Visioは、VBAからシェイプが1つ生成・移動されるたびに画面を再描画しようとする。数千個のシェイプを持つ拠点図面を同期させる場合、これを切るだけで実行時間が 数分から数秒へ 劇的に短縮される。
2. 外部データベース(DB)やExcelとのハイブリッド連携
もしマスター図面すら持たず、データベースやExcelのマスターテーブルから情報を引いてVisio図面群を動的に生成・同期したい場合は、上記の `srcShape` の部分をDBレコードのループに置き換え、`tgtPage.Drop` の後にカスタムプロパティを流し込むアーキテクチャに拡張すればよい。
3. エラー時のクリーンアップ
プロシージャ内でエラーが発生した際、`ScreenUpdating = False` のままマクロが終了すると、Visioの画面がフリーズしたように見え、ユーザーがパニックを起こす。必ず `Finally` ラベル等を用いた確実な状態復旧(トランザクション的な発想)を組み込むこと。
複数ドキュメントの掌握は、Visio VBAプログラミングにおける一つの到達点だ。
泥臭いコピペコードから脱却し、オブジェクトの参照とメタデータによる論理的結合をマスターしたあなたなら、どんなに複雑なエンタープライズ図面管理システムも自在に構築できるはずだ。健闘を祈る。
