Visio VBAを掌握する極限の知見:マスターリンクが切れた図形の復旧と再紐付け
開発プロジェクトの現場において、Visio図面の保守性を根本から破壊する最大の元凶をご存知だろうか。
それは、「他図面からのコピペによるマスター情報の断絶(ローカル化)」である。
現場のユーザーは悪びれることなく、別ファイルの図面からお気に入りのシェイプをコピー&ペーストする。その瞬間、Visioの裏側では静かに悲劇が起きている。本来、ステンシルや図面固有のマスター(`Master`オブジェクト)に紐づくべきシェイプが、その絆を断ち切られ、ただの「孤立した図形(ローカルシェイプ)」へと変貌してしまうのだ。
この状態を放置すると何が起きるか?
- マスターを一括修正しても、コピペされた図形だけデザインが同期されない。
- ドキュメント内にゾンビのような不要マスターが乱立し、ファイルサイズが肥大化する。
- 自動集計や外部DB連携の際、オブジェクトの型やプロパティの不整合でマクロがクラッシュする。
今回は、この腐敗したマスターリンクをVBAによって検出し、本来あるべきマスターへと強制的に再紐付け(Re-mastering)するための極限の知見を伝授する。
—
1. なぜマスターリンクは切れるのか?(オブジェクトモデルの深淵)
Visioのアーキテクチャにおいて、シェイプ(`Shape`オブジェクト)は常に親である`Master`オブジェクトへの参照(`Shape.Master`プロパティ)を保持している。
正常な状態であれば、`Shape.Master`は有効なオブジェクトを指すが、外部ドキュメントからのコピペや、特定のシェイプ操作が行われると、以下の現象が発生する。
1. マスターの不在: 貼り付け先のドキュメント(`ActiveDocument`)に、元のマスター名が存在しない場合、Visioは自動的にそのシェイプのマスター情報を内部に取り込み、ローカルなマスターとしてドキュメントに追加する。
2. リンクの完全切断: 場合によってはマスターとの関連付けが完全に失われ、`Shape.Master`が `Nothing` を返す「完全な野良シェイプ」と化す。
これを人力で修復するのは不可能に近い。特に数百のシェイプが入り乱れるフロー図やプラント図面では、VBAによる自動修復ロジックが唯一にして最大の防衛策となる。
—
2. 堅牢な設計:マスター判定と再紐付けのアルゴリズム
実務で使えるツールを構築するためには、以下の要件を満たす必要がある。
- 再帰的な走査: グループ化されたシェイプ(`Shapes.Count > 0`かつ`Type = visTypeGroup`)の内部に存在する子シェイプも見落とさないこと。
- 正確な名前照合: 外部ステンシル、またはドキュメント内の既存マスターから、正しい名前(`NameU` または `Name`)を持つものを厳密に特定すること。
- 例外処理: マスターが存在しない場合のフォールバック(スキップ、またはデフォルト図形への置換)を考慮すること。
Visio VBAにおける致命的な罠
`Shape.Master`プロパティに直接別のマスターオブジェクトを代入することはできない。Visio VBAでマスターを再紐付けするには、実質的に「既存のマスターから新しいシェイプを生成して置き換える」か、ドキュメント内のMastersコレクションとの整合性をプログラムで強制的に再構築する必要がある。
実務上、最も安全で確実なアプローチは、「切断されたシェイプのマスター名を特定し、アクティブドキュメントの `Masters` コレクションから該当マスターを取得して、`Shape.Drop` または専用のメソッドで再接続する」ことである。しかし、単純なプロパティ書き換えでは動かないため、ここでは「マスター情報の修復(ローカルマスターの統合)」と「正しいマスターへのすげ替え」を実装するプロダクションコードを提示する。
—
3. プロダクションコード:マスターリンク自動修復エンジン
以下のコードは、アクティブページの全シェイプを走査し、マスターリンクが切断されている、あるいは意図しないローカルマスターを向いているシェイプを検出し、指定したマスターへ強制的に再紐付けを行う実用モジュールである。
Option Explicit
‘ ==============================================================================
‘ módulo名: modMasterRepairer
‘ 概要 : マスターリンクが切断されたシェイプを検出し、指定マスターに再紐付けする
‘ ==============================================================================
Public Sub RepairShapeMasters()
Dim vsoPage As Visio.Page
Set vsoPage = ActivePage
If vsoPage Is Nothing Then
MsgBox “アクティブなページが存在しません。”, vbCritical, “エラー”
Exit Sub
End If
‘ トランザクション処理の開始(パフォーマンス向上と一括Undoのため)
Dim undoScopeID As Long
undoScopeID = Application.BeginUndoScope(“マスターリンクの修復”)
On Error GoTo ErrorHandler
Dim repairedCount As Long
repairedCount = 0
‘ ページの全シェイプを再帰的に走査
Call ProcessShapes(vsoPage.Shapes, repairedCount)
Application.EndUndoScope undoScopeID, True
MsgBox “修復処理が完了しました。” & vbCrLf & “修復されたシェイプ数: ” & repairedCount, vbInformation, “完了”
Exit Sub
ErrorHandler:
Application.EndUndoScope undoScopeID, False
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”
End Sub
‘ ——————————————————————————
‘ シェイプコレクションの再帰的走査プロシージャ
‘ ——————————————————————————
Private Sub ProcessShapes(ByVal vsoShapes As Visio.Shapes, ByRef count As Long)
Dim vsoShape As Visio.Shape
Dim i As Long
‘ コレクションの逆順ループ(グループ解除や削除などが発生しても安全なように)
For i = vsoShapes.Count To 1 Step -1
Set vsoShape = vsoShapes(i)
‘ 1. グループシェイプの場合は内部を再帰処理
If vsoShape.Type = visTypeGroup Then
Call ProcessShapes(vsoShape.Shapes, count)
End If
‘ 2. マスターリンクの状態を評価
If IsMasterDisconnected(vsoShape) Then
‘ ここでは例として、シェイプのセーフティネーム(ローカル名)を元に
‘ ドキュメント内の本来あるべきマスターを再割り当てするロジックを呼ぶ
If TryRelinkMaster(vsoShape) Then
count = count + 1
End If
End If
Next i
End Sub
‘ ——————————————————————————
‘ マスターリンクが切断されているか、異常な状態かを判定
‘ ——————————————————————————
Private Function IsMasterDisconnected(ByVal vsoShape As Visio.Shape) As Boolean
‘ ガイドや特定の一過性シェイプは対象外とする
If vsoShape.Containertype = visContainerTypeCallout Then GoTo NotTarget
On Error Resume Next
Dim vsoMaster As Visio.Master
Set vsoMaster = vsoShape.Master
If Err.Number <> 0 Then
‘ マスター取得時にエラーが発生する場合はリンク切れ
IsMasterDisconnected = True
Exit Function
End If
On Error GoTo 0
‘ MasterプロパティがNothing、またはドキュメントのマスターとして正しく登録されていない場合
If vsoMaster Is Nothing Then
IsMasterDisconnected = True
Else
‘ 必要に応じて、特定の命名規則から外れているかをチェック
‘ 例: マスター名が “Sheet.” で始まるものはローカル化された偽マスターの可能性が高い
If Left$(vsoMaster.Name, 6) = “Sheet.” Then
IsMasterDisconnected = True
Else
IsMasterDisconnected = False
End If
End If
Exit Function
NotTarget:
IsMasterDisconnected = False
End Function
‘ ——————————————————————————
‘ マスターの再紐付けを実行するコアロジック
‘ ——————————————————————————
Private Function TryRelinkMaster(ByRef vsoShape As Visio.Shape) As Boolean
On Error GoTo ErrHandler
Dim docMasters As Visio.Masters
Set docMasters = ActiveDocument.Masters
‘ 【重要】本来紐づくべきマスター名をシェイプのプロパティや
‘ 外部定義(セルのカスタムプロパティ等)から逆引きする
‘ ここでは例として、シェイプのMasterNaUセル、またはShape.Nameから推測
Dim targetMasterName As String
targetMasterName = GetTargetMasterName(vsoShape)
If targetMasterName = “” Then
TryRelinkMaster = False
Exit Function
End If
Dim targetMaster As Visio.Master
Set targetMaster = Nothing
Dim m As Long
For m = 1 To docMasters.Count
If docMasters(m).NameU = targetMasterName Or docMasters(m).Name = targetMasterName Then
Set targetMaster = docMasters(m)
Exit For
End If
Next m
‘ ドキュメント内にマスターが見つからない場合は終了
If targetMaster Is Nothing Then
TryRelinkMaster = False
Exit Function
End If
‘ 注意: 既存シェイプのMasterプロパティを直接書き換えることはできないため、
‘ 正しいマスターから新しいシェイプをドロップし、位置とプロパティを引き継がせる設計が最も堅牢。
‘ ※実務では図形の座標、テキスト、カスタムプロパティ(Prop.)を退避して置換する。
Dim pinX As Double, pinY As Double
pinX = vsoShape.Cells(“PinX”).ResultIU
pinY = vsoShape.Cells(“PinY”).ResultIU
Dim shapeText As String
shapeText = vsoShape.Text
‘ 新しいマスターからシェイプを生成
Dim newShape As Visio.Shape
Set newShape = ActivePage.Drop(targetMaster, pinX, pinY)
If Not newShape Is Nothing Then
‘ プロパティの継承(テキストなど)
newShape.Text = shapeText
‘ 古い(リンクの切れた)シェイプを削除
vsoShape.Delete
TryRelinkMaster = True
Exit Function
End If
ErrHandler:
TryRelinkMaster = False
End Function
‘ ——————————————————————————
‘ シェイプが本来持つべきマスター名を特定する補助関数
‘ ——————————————————————————
Private Function GetTargetMasterName(ByVal vsoShape As Visio.Shape) As String
‘ 実務要件に合わせて実装する。
‘ 例: シェイプのUserセルやShapeData(Prop)に元のマスター名を保持させている場合、それを読み取る。
On Error Resume Next
Dim origName As String
origName = vsoShape.Cells(“Prop.OriginalMaster”).ResultStr(“”)
If origName <> “” Then
GetTargetMasterName = origName
Else
‘ フォールバック: シェイプのマスター名から推測(実装の仕様に応じて変更)
GetTargetMasterName = “”
End If
End Function
—
4. チーフアーキテクトからの実務アドバイス
このコードをそのまま導入するにあたり、以下の設計上のポイントを肝に銘じておいてほしい。
1. トランザクション(UndoScope)の徹底:
Visioの操作は一歩間違えると元に戻せなくなる。必ず `BeginUndoScope` と `EndUndoScope` で囲み、マクロの途中でエラーが発生しても確実に処理がロールバックされる構造にすること。
2. シェイプ置換時のデータ消失防止:
上記のサンプルコードでは `vsoShape.Delete` を行っている。もしシェイプに膨大なカスタムプロパティ(ShapeData)や外部データベース連携用のIDが紐づいている場合、削除する前にそれらの値を一次変数やDictionaryに退避させ、新しくドロップした `newShape` 側に転記する処理を必ず追加すること。
3. マスター名の規約化:
現場のオペレーターが自由にコピペする文化を完全に断つことはできない。だからこそ、「図形を配置した瞬間に、ユーザー定義セル(`User.MasterName`)に自身のマスター名を書き込むイベントハンドラ(`VisEventProc`)」をあらかじめDocumentに仕込んでおく。これが、マスターリンク切れを未然に防ぐ究極のアーキテクチャである。
業務自動化エンジニアとしてのプライドにかけて、ただ動くだけのコードを書くな。保守性、パフォーマンス、そして予期せぬエラーに対する強靭さを備えたシステムを構築し、現場の非効率を根絶やしにしてほしい。
