【テクニカル・上級編】【上級】Shape.UniqueIDによる永続的トラッキング:ファイル保存・再読み込み後も図形を追跡する – Visio VBA解析バイブル

スポンサーリンク

【上級】Shape.UniqueIDによる永続的トラッキング:ファイル保存・再読み込み後も図形を追跡する

Visio VBAにおける図形識別は、多くの開発者が最初に直面する「見えない壁」だ。
通常の開発現場では、図形の特定に `Shape.Name` や `Shape.ID` を使う。しかし、これらはファイルを開き直したり、図形をコピー&ペーストしたり、グループ化を解除したりした瞬間に平然と書き換わる。揮発性のIDに依存したシステムは、運用フェーズに入った途端にデータ整合性の崩壊という致命的なバグを引き起こす。

外部データベースや上位システムとVisio図面を完全に同期させたいシニアエンジニアにとって、避けて通れないのが `Shape.UniqueID` の完全掌握である。

今回は、ファイル保存・再読み込み後も図形を半永久的に追跡し、外部システムとの強固なデータパイプラインを構築するための極限の知見を公開する。

—

1. 揮発性ID(`Shape.ID`)の罠と、`UniqueID`の本質

Visioの `Shape.ID` は、あくまで「現在のページ内でのインデックス」に過ぎない。ドキュメントを保存して閉じれば、その番号の寿命は切れる。一方、`UniqueID` は、Visioが内部で管理するUUID(Universally Unique Identifier)ベースの永続的識別子だ。

Visioの `UniqueID` には、取得方法とスコープを制御するためのフラグ(`VisUniqueIDArgs`)が存在する。ここを誤ると、予期せぬパフォーマンス低下や意図しないIDの生成を招く。

ユニークID取得・設定の基本方針

  • `visGetUIDExist`: 既存のUniqueIDがあればそれを取得し、なければ空文字を返す。新規発番させないための安全確認用。
  • `visGetUIDNew`: 既存があればそれを返し、なければ新しく生成して付与する。実務では通常これを使用する。
  • `visGetUIDGlobal`: ドキュメント全体(複数ページにまたがるスコープ)で一意性を担保する。

—

2. 【実践】ファイル再読み込み後も図形を特定する堅牢なVBAコード

以下のコードは、図形に永続的なUniqueIDをバインドし、一度ファイルを閉じて再読み込みした後でも、外部ストレージ(ここではイミディエイトウィンドウやカスタムプロパティを模擬)から同一の図形を完璧に復元・特定する実証コードである。

Option Explicit

‘ ==============================================================================
‘ 2次元配列またはDictionaryの代わりに、UniqueIDをキーにして図形を特定するクラス的モジュール
‘ ==============================================================================

Public Sub DemonstratePersistentTracking()
Dim vsoPage As Visio.Page
Set vsoPage = ActivePage

If vsoPage Is Nothing Then Exit Sub

‘ 1. 図形を新規作成し、永続的ID(UniqueID)を強制付与する
Dim vsoShape As Visio.Shape
Set vsoShape = vsoPage.DrawRectangle(1, 5, 3, 4)
vsoShape.Text = “Enterprise Node-01”

‘ ユニークIDを取得(存在しない場合は新規採番)
Dim targetUID As String
targetUID = vsoShape.UniqueID(visGetUIDNew)

Debug.Print “【生成】図形名: ” & vsoShape.Name & ” | 永続UniqueID: ” & targetUID

‘ ※ここで一度ファイルを保存し、VBEを閉じて開き直したと仮定する ※

‘ 2. 再読み込み後の世界:UniqueIDから図形をミリ秒単位で逆引きする
Dim recoveredShape As Visio.Shape
Set recoveredShape = FindShapeByUniqueID(vsoPage, targetUID)

If Not recoveredShape Is Nothing Then
Debug.Print “【復元成功】追跡対象の図形を特定しました: ” & recoveredShape.Text
‘ 外部DBとの同期処理などをここに記述
recoveredShape.Text = “Enterprise Node-01 [Synced]”
Else
Debug.Print “【エラー】指定されたUniqueIDを持つ図形は存在しません。”
End If

‘ クリーンアップ(オブジェクトの明示的解放)
Set recoveredShape = Nothing
Set vsoShape = Nothing
Set vsoPage = Nothing
End Sub

‘ ==============================================================================
‘ UniqueIDをキーにしてページ内から図形をO(N)で高速検索する関数
‘ ==============================================================================
Private Function FindShapeByUniqueID(vsoPage As Visio.Page, targetUID As String) As Visio.Shape
Dim vsoShape As Visio.Shape
Dim currentUID As String

On Error GoTo ErrorHandler

For Each vsoShape in vsoPage.Shapes
‘ 各図形のUniqueIDを取得(存在確認のみ、新規採番はしない)
currentUID = vsoShape.UniqueID(visGetUIDExist)

If currentUID = targetUID Then
Set FindShapeByUniqueID = vsoShape
Exit Function
End If

‘ グループ図形の子要素(サブシェイプ)まで再帰的に走査する場合の拡張ポイント
If vsoShape.Type = visTypeGroup Then
Set FindShapeByUniqueID = FindShapeInSubShapes(vsoShape, targetUID)
If Not FindShapeByUniqueID Is Nothing Then Exit Function
End If
Next vsoShape

Set FindShapeByUniqueID = Nothing
Exit Function

ErrorHandler:
‘ 予期せぬCOMエラーのハンドリング
Debug.Print “Error in FindShapeByUniqueID: ” & Err.Description
Set FindShapeByUniqueID = Nothing
End Function

‘ ==============================================================================
‘ グループ内のサブシェイプを再帰的に走査するヘルパー関数
‘ ==============================================================================
Private Function FindShapeInSubShapes(vsoParentShape As Visio.Shape, targetUID As String) As Visio.Shape
Dim vsoSubShape As Visio.Shape
Dim currentUID As String

For Each vsoSubShape in vsoParentShape.Shapes
currentUID = vsoSubShape.UniqueID(visGetUIDExist)
If currentUID = targetUID Then
Set FindShapeInSubShapes = vsoSubShape
Exit Function
End If

If vsoSubShape.Type = visTypeGroup Then
Set FindShapeInSubShapes = FindShapeInSubShapes(vsoSubShape, targetUID)
If Not FindShapeInSubShapes Is Nothing Then Exit Function
End If
Next vsoSubShape

Set FindShapeInSubShapes = Nothing
End Function

—

3. パフォーマンスの罠:大規模図面におけるO(N)走査の回避

上記のコードでは、ページ内のすべての図形を `For Each` で走査している。数千の図形が存在するプラント図や大規模ネットワーク図において、これを毎回のイベント(例えば `SelectionChanged` や外部DBからのポーリング)で実行すると、確実にUIスレッドがフリーズする。

シニアエンジニアが取るべきアプローチは、「Scripting.Dictionary」によるインデックスのキャッシュ化、あるいは Shapeのカスタムプロパティ(ShapeSheet)へのUIDの逆引きキャッシュ である。

さらに、Visio VBAのメモリ管理において最も重要なのは、COMオブジェクトのループ内での連鎖的リークを防ぐことだ。`For Each` の中で取得したインスタンスは、スコープを抜けるまでメモリ上に残り続ける。大規模な走査を行う場合は、こまめなオブジェクト変数の解放(`Set vsoShape = Nothing`)と、エラーハンドリングの徹底が不可欠となる。

—

4. 外部データベース(RDB / NoSQL)との連携アーキテクチャ

UniqueIDの実践的な価値は、外部システムとの「主キー(Primary Key)」としての結合にある。

1. 初期配置時: ユーザーがVisio上で図形をドロップした際、VBAの `Document_ShapeAdded` イベントをフックする。
2. ID自動付与: イベント内で `vsoShape.UniqueID(visGetUIDNew)` を実行し、生成された文字列を取得する。
3. DB同期: 取得した `UniqueID` を主キー、または外部キーとして、RDB(SQL Server / PostgreSQL等)の資産マスターやCI(構成管理)データベースにINSERTする。
4. 再読み込み・更新時: 外部システム側から「この機器のステータスが異常値になった」という通知を受け取った際、Visio側のアドインまたはVBAマクロが `UniqueID` を頼りに該当図形の配色(`FillForegnd` セル)をリアルタイムに赤色へ変更する。

このループが完成した瞬間、Visioは単なる「お絵描きツール」から、「生きたビジュアル・ダッシュボード(Digital Twinのフロントエンド)」へと昇華する。

—

総括

`Shape.UniqueID` は、レガシーなVisio VBA開発をモダンなエンタープライズシステムへと引き上げるための最強のカードである。
揮発性のIDに頼るコードは今すぐ捨て去り、永続的トラッキングを前提とした堅牢なオブジェクトライフサイクル管理を実装してほしい。実務の荒波に耐えうるシステムとは、こうした細部へのこだわりとアーキテクチャの美しさの積算によってのみ構築されるのだ。

タイトルとURLをコピーしました