Visio VBAを掌握する極限の知見:Shape.NameIDの落とし穴とユニーク名管理術
業務自動化の現場において、Microsoft Visioはフローチャートやネットワーク図の自動生成という強力な武器となる。しかし、APIの仕様やオブジェクトモデルの挙動を深く理解せずにコードを書くと、中規模以上の図面生成で必ず「不可解なバグ」や「パフォーマンスの急低下」に直面する。
その代表格が、図形の名前(NameおよびNameID)にまつわる罠だ。
今回は、動的に図形を生成・削除するプロセスにおいて、名前の衝突を防ぎ、プログラム側から確実にターゲットを追跡・制御するための「ユニーク名管理術」を伝授する。
—
1. なぜ「デフォルトの名前」に頼ると破綻するのか?
Visio VBAで新しい図形をドロップ、あるいは `DrawRectangle` などのメソッドで生成すると、Visioは自動的に `Sheet.1`, `Sheet.2` といったデフォルト名を付与する。
一見すると連番で管理されているように見えるが、ここに大きな落とし穴がある。
罠1:図形の削除による「名前の再利用と衝突」
例えば、`Sheet.1` という図形を生成し、何らかの処理の途中でそれを削除したとする。その後、新しく図形を生成すると、Visioの内部カウンターの状態によっては、再び `Sheet.1` という名前が別の図形に割り振られることがある。
これにより、古いインデックスをキャッシュしていたプログラムが、意図しない別の図形を操作してしまう「誤爆バグ」が発生する。
罠2:NameとNameIDの決定的な違い
Visioの `Shape` オブジェクトには、UIや開発者が変更可能な `Name` プロパティと、Visioが内部的に一意性を保証しようとする `NameID` プロパティが存在する。
しかし、`NameID`(例: `Sheet.1!` のような文字列)は、ドキュメントの構造変化や図形のコピー&ペースト、さらには保存と再オープンによって容易に変動・再採番される。これをコードのハードコードや永続的な識別子として頼るのは、砂上の楼閣を築くようなものだ。
—
2. 堅牢な設計:カスタムID(User-Defined Cells)による完全制御
では、実務の現場でどう設計すべきか?
答えはシンプルだ。Visioが自動付与する名前に依存せず、開発者が制御する一意の識別子(ID)をShapeのカスタムセル(User-Defined Cells)に埋め込み、検索・特定を行うこと。
さらに、Visioの検索エンジンを効率的に使うために、Shapeの `Name` プロパティ自体を独自のビジネスロジック用IDに書き換えてしまうアプローチが極めて有効である。
アーキテクチャの原則
1. 生成時に固有のキーを付与する:UUIDや業務上のコード(例: `NODE_001_A`)を生成直後に `Shape.Name` に設定する。
2. NameIDは「今のセッション内のポインタ」と割り切る:永続化や外部DBとの連携には絶対に使わない。
3. 存在チェックを必ず挟む:イテレーションや一括処理では、名前から直接オブジェクトを取得するのではなく、エラーハンドリングを伴う安全なラッパー関数経由でアクセスする。
—
3. 【プロダクションコード】堅牢な図形生成と検索の模範実装
以下のコードは、実際の業務自動化プロジェクトで使用できる、名前衝突を防ぎつつ動的に図形を管理するためのモジュールテンプレートだ。コピペしてそのままプロジェクトに組み込むことができる。
Option Explicit
‘ ==============================================================================
‘ モジュール名: MdlShapeManager
‘ 概要: ShapeのName衝突を防ぎ、カスタムIDで確実に追跡・管理するための実践コード
‘ ==============================================================================
‘ ——————————————————————————
‘ 1. ユニーク名義の図形生成ラッパー
‘ ——————————————————————————
Public Function CreateManagedShape(ByRef targetPage As Visio.Page, _
ByVal masterName As String, _
ByVal businessKey As String, _
ByVal posX As Double, _
ByVal posY As Double) As Visio.Shape
Dim shpMaster As Visio.Master
Dim newShp As Visio.Shape
On Error GoTo ErrorHandler
‘ マスターシェイプの取得(ステンシルが開いている前提)
Set shpMaster = Visio.Documents.Item(“Basic Shapes.vssx”).Masters.ItemBstr(masterName)
‘ ページに図形をドロップ
Set newShp = targetPage.Drop(shpMaster, posX, posY)
‘ 【重要】デフォルトのName衝突を回避するため、独自のビジネスキーをNameに強制適用
‘ ※同一ページ内に同名のShapeが存在する場合はエラーになるため、事前に削除・変更ロジックを入れること
If ShapeExistsOnPage(targetPage, businessKey) Then
‘ 既存同名図形が存在する場合のハンドリング(必要に応じて連番付与や上書き)
targetPage.Shapes.Item(businessKey).Delete
End If
newShp.Name = businessKey
‘ 念のため、Shapeのセル(User-Defined Cells)にもIDを書き込んで二重担保とする
Dim cellName As String
cellName = “User.BusinessID”
If Not newShp.CellExists(cellName, visExistsLocally) Then
newShp.AddNamedRow visSectionUser, “BusinessID”, 0
End If
newShp.Cells(cellName).FormulaU = “””” & businessKey & “”””
Set CreateManagedShape = newShp
Exit Function
ErrorHandler:
MsgBox “図形生成エラー: ” & Err.Description, vbCritical, “ShapeManager”
Set CreateManagedShape = Nothing
End Function
‘ ——————————————————————————
‘ 2. 指定したビジネスキー(ユニーク名)で図形を安全に取得する関数
‘ ——————————————————————————
Public Function GetShapeByBusinessKey(ByRef targetPage As Visio.Page, ByVal businessKey As String) As Visio.Shape
Dim shp As Visio.Shape
On Error Resume Next
‘ Nameプロパティを直接指定して取得を試みる
Set shp = targetPage.Shapes.Item(businessKey)
On Error GoTo 0
If Not shp Is Nothing Then
Set GetShapeByBusinessKey = shp
Exit Function
End If
‘万が一Nameが変更されている場合のフォールバック(Userセルからの走査)
For Each shp in targetPage.Shapes
If shp.CellExists(“User.BusinessID”, visExistsAnywhere) Then
If shp.Cells(“User.BusinessID”).ResultStr(“”) = businessKey Then
Set GetShapeByBusinessKey = shp
Exit Function
End If
End If
Next shp
‘ 見つからない場合
Set GetShapeByBusinessKey = Nothing
End Function
‘ ——————————————————————————
‘ 3. 図形の存在確認ヘルパー
‘ ——————————————————————————
Public Function ShapeExistsOnPage(ByRef targetPage As Visio.Page, ByVal businessKey As String) As Boolean
Dim shp As Visio.Shape
Set shp = GetShapeByBusinessKey(targetPage, businessKey)
ShapeExistsOnPage = Not (shp Is Nothing)
End Function
—
4. データベースおよび外部ファイル連携時の注意点
Excel、Access、あるいは外部のWeb APIからデータを取得し、それを元にVisio図面を自動構築・更新(差分同期)するシステムを構築する場合、「Visio内部のオブジェクトID(Shape.IDやNameID)をDBの主キーとして保存する」という設計は絶対に避けるべきである。
データベース連携の鉄則
- DB側のIDをVisioに持たせる:前述のコード例のように、外部DBのレコードID(例: `Record_ID_1042`)を `Shape.Name` および `User.BusinessID` に必ず保持させる。
- 再描画・更新時のライフサイクル:
1. 既存の図面を開く。
2. DBから最新のマスターデータを取得する。
3. Visio上の全シェイプをスキャンし、DBのIDリストに存在しないものを削除(または非表示)にする。
4. 存在し、かつ変更があるものはプロパティ(テキストや位置)を更新する。
5. 存在しないものは新規作成する。
この「IDベースのイデポテント(べき等)な更新処理」を実装しておけば、何度プログラムを走らせても図形の名前衝突やゾンビ図形の発生に悩まされることはなくなる。
—
チーフアーキテクトからの総括
Visio VBAは、表面上のメソッドを叩くだけであれば容易に見える。しかし、オブジェクトのライフサイクル、名前解決のメカニズム、そして永続化の限界を理解していないと、現場でスケールしない「動くだけの脆弱なマクロ」しか生み出せない。
「図形の名前はVisio任せにせず、すべてビジネスロジック側のIDで完全統御する」
この原則をコードに落とし込むことこそが、プロダクション品質の業務自動化システムを構築するための唯一にして最大の近道である。プロフェッショナルとして、常に「予測可能で堅牢なコード」をデザインし続けてほしい。
