【テクニカル・上級編】Shapeオブジェクトの識別子マスター:ID・Name・NameUの違いと使い分けの鉄則 – Visio VBA解析バイブル

スポンサーリンク

Shapeオブジェクトの識別子マスター:ID・Name・NameUの違いと使い分けの鉄則

VisioのVBA開発において、避けて通れないのが「図形の特定」という最初の関門だ。
`ActivePage.Shapes.Item(…)` や `Page.ShapesFromID(…)` を記述する際、君たちはどの識別子を使っているだろうか?

`ID`、`Name`、そして `NameU`。
これら3つのプロパティは、一見するとどれも同じ「図形を指し示す名前や番号」のように思える。しかし、この違いを理解せずにコードを書くことは、時限爆弾を抱えたシステムを構築するようなものだ。図面のローカライズ、動的な図形生成、そしてシステム間連携において、この識別子の選択ミスは即座に致命的なバグを引き起こす。

今回は、Visioのオブジェクトモデルの深層を知り尽くしたアーキテクトの視点から、これら3つの識別子の正体を完全に暴き、二度と壊れない堅牢な図形取得ロジックの構築法を伝授する。

1. 識別子の正体:ID・Name・NameUのアーキテクチャ

まずは、それぞれのプロパティが持つ本質的なライフサイクルとスコープを定義する。

| 識別子 | スコープ / 一意性 | 可変性 | 多言語対応 | 主な用途 |
| :— | :— | :— | :— | :— |
| ID | ページ内で一意 (整数) | 変動する (削除・追加で変化) | 依存しない | 一時的なイテレーション、高速な図形参照 |
| Name | ページ内で一意 (文字列) | 変更可能 (ユーザー操作で変わる) | 依存する (日本語OSなら日本語) | UI上の表示、簡易的なデバッグ |
| NameU | ページ内で一意 (文字列) | 不変 (システム内部で固定) | 依存しない (常に英語/Language-Invariant) | システム連携、永続的な図形識別 |

ID:速度の王だが、永続性ゼロの儚い存在

`Shape.ID` は、Visioのページ(Page)オブジェクトが内部管理する整数値(Long型)だ。
ページ内での描画順や生成・削除の履歴に応じて動的に割り振られる。最大のメリットは検索コストが極めて低いこと。`Page.ShapesFromID(id)` を使えば、文字列比較のオーバーヘッドなしにO(1)に近い速度でオブジェクトにアクセスできる。

しかし、図形を削除したり、Undo/Redoを行ったりすると、IDは容易に付け替わる。保存して再読み込みした瞬間にIDがガラリと変わることも珍しくない。したがって、データベースや外部ファイルに「図形ID=5」などと永続的な関連付けを保存するのは、システムアーキテクチャの設計ミスである。

Name:人間が見るためのローカライズされた名前

`Shape.Name` は、ステンシルからドロップされた際や、ユーザーがシェイプシートで任意に書き換える文字列(例: `”プロセス.12″`, `”長方形”`)だ。
これは厄介なことに、実行環境のOS言語に完全に依存する。日本語版Visioであれば日本語の文字列になり、英語版であれば `”Process.12″` になる。
これをコードのハードコード条件に組み込んだ瞬間、グローバル展開している企業や、異なる言語圏のユーザーが混在する環境でシステムはクラッシュする。

NameU:システム連携の絶対的な基軸(Universal Name)

`Shape.NameU`(ユニバーサル名)こそ、我々シニアエンジニアが依拠すべき唯一の絶対的な識別子である。
これはVisioエンジン内部で一意に割り振られる英語ベースの識別子であり、OSの言語設定がどう変わろうとも絶対に変化しない。さらに、ユーザーがUI上で図形のテキストや表示名を変更しても、`NameU` は微動だにしない。

外部のRDBやAPI、Excelマクロと連携して「特定の機器を表す図形」を特定したい場合、コード内で指定すべきは常に `NameU` 一択となる。

2. 【アンチパターン】なぜ「Name」や「ID」のハードコードは崩壊するのか?

実務でよく見かける、最も危険なコードの例を見てみよう。

‘ 【危険なアンチパターン】
Sub UpdateProcessStatus_Bad()
Dim shp As Visio.Shape
‘ 日本語環境でのみ動作する、言語依存のName指定
Set shp = ActivePage.Shapes.Item(“プロセス.12”)

shp.Cells(“FillForeg2”).FormulaU = “RGB(255, 0, 0)”
End Sub

このコードの何が問題か?
1. 別のPC(英語OS)で実行した瞬間、`”プロセス.12″` という名前のシェイプが存在しないため、`Run-time error ‘-20324’`(指定された名前の項目が見つかりません)が発生して即死する。
2. ユーザーが万が一、図形の名前やマスターのコピーを作成した際、サフィックス(`.1`, `.2`)が変わり、コードが追従できなくなる。

3. 鉄則:壊れない図形取得ロジックの構築法

では、多言語環境や図面の構造変更に耐えうる、真に堅牢な図形取得ロジックとはどのようなものか。
正解は、「NameUをイテレーションで走査してヒットさせる」 もしくは 「カスタムプロパティ(Prop)やUserセクションを活用する」 ことだ。

以下に、実務の現場でそのまま流用できるモジュールを提示する。

実装例:NameUによる安全な図形取得関数

Option Explicit

‘ =========================================================================
‘ 概要: 指定されたNameUを持つShapeオブジェクトを安全に取得する
‘ 引数: targetPage – 検索対象のPageオブジェクト
‘ universalName – 検索する図形のNameU (例: “ProcessNode.32”)
‘ 戻り値: 見つかった場合はShapeオブジェクト、見つからない場合はNothing
‘ =========================================================================
Public Function GetShapeByNameU(targetPage As Visio.Page, universalName As String) As Visio.Shape
Dim shp As Visio.Shape
Dim foundShp As Visio.Shape

Set foundShp = Nothing

‘ パフォーマンスとメモリ安全性を考慮したイテレーション
For Each shp In targetPage.Shapes
‘ NameUを完全一致で比較 (大文字小文字を区別しない場合はStrCompを使用)
If StrComp(shp.NameU, universalName, vbTextCompare) = 0 Then
Set foundShp = shp
Exit For
End If

‘ グループ図形内のサブシェイプ(子図形)も再帰的に走査する必要がある場合
‘ ここに関数を拡張する(今回は省略)
Next shp

Set GetShapeByNameU = foundShp

‘ VBAのオブジェクト参照を明示的に解放 (メモリリーク防止の作法)
Set shp = Nothing
End Function

4. 極限の知見:大規模図面におけるパフォーマンス最適化とメモリ管理

Visio VBAで数千個の図形を持つ巨大なプラント図やネットワーク図を扱うとき、`For Each shp In Page.Shapes` のような愚直なループは、ガベージコレクションの未熟なVBA環境においてメモリ肥大化(メモリリーク)の原因となる。

ここでチーフアーキテクトとしての極限の知見を授けよう。

1. `ShapesFromID` とのハイブリッド戦略

図形のIDが既知である、あるいはログや一時キャッシュからIDが引ける場合は、`NameU` の文字列比較よりも `Page.ShapesFromID()` を優先すべきだ。文字列比較はCPUサイクルを食う。
初期探索やマスタ特定には `NameU` を使い、一度取得したシェイプのポインタ(参照)をセッション中保持できるのであれば、IDベースでアクセスするのが最速のパスとなる。

2. シェイプシートの `User` セクションとの組み合わせ

さらに高度なシステム連携において、ユーザーがVisio上で図形をコピー&ペーストすると、Visioは自動的に新しい `NameU`(一意なサフィックス付き)を採番してしまう。これにより、プログラム側で固定の `NameU` をハードコードしても、図形を複製された瞬間にロジックが破綻する。

この課題を完全にクリアする唯一の解法が、シェイプシートの `User.SystemID` などのカスタムセクションに一意のUUIDを書き込んでおく手法だ。

‘ 図形に不変のシステムID(UUID等)をUserセクションに刻み込む
Public Sub StampSystemID(shp As Visio.Shape, uniqueID As String)
Dim cell As Visio.Cell

‘ User.SystemIDセルが存在しない場合は追加される
Set cell = shp.Cells(“User.SystemID”)
cell.FormulaU = “””” & uniqueID & “”””

Set cell = Nothing
End Sub

‘ UserセクションのSystemIDから図形を特定する究極のロジック
Public Function GetShapeBySystemID(targetPage As Visio.Page, targetSystemID As String) As Visio.Shape
Dim shp As Visio.Shape
Dim cell As Visio.Cell
Dim foundShp As Visio.Shape

Set foundShp = Nothing

For Each shp In targetPage.Shapes
‘ User.SystemID セルが存在するか安全にチェック
On Error Resume Next
Set cell = shp.Cells(“User.SystemID”)
If Err.Number = 0 Then
‘ プレフィックス等のゴミを除去した値で比較
If cell.ResultStr(visNoUnit) = targetSystemID Then
Set foundShp = shp
On Error GoTo 0
Exit For
End If
End If
On Error GoTo 0
Next shp

Set GetShapeBySystemID = foundShp
Set shp = Nothing
Set cell = Nothing
End Function

このアプローチを取れば、図形がコピーされようが、IDが変わろうが、言語環境が変わろうが、システムはビクともしない。

5. 総括

Visioの図形識別子に関する知見をまとめる。

1. `ID` は速度の味方だが、ライフサイクルが短いため永続化には使わない。
2. `Name` はローカライズに汚染されるため、システムコードでは絶対に使用しない。
3. `NameU` は多言語・不変性の観点から、標準的な図形特定の基軸とする。
4. 極限のシステム要件(コピー&ペーストへの耐性など)においては、シェイプシートの `User` セクションに独自のUUIDを埋め込む設計を取り入れよ。

VBAコードの美しさは、環境の変化に対する「しなやかさ」と、メモリ管理における「厳格さ」の共存にある。この鉄則を胸に、明日からの実装の品質を一段上のステージへと引き上げてほしい。

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