【テクニカル・上級編】Shape.NameIDの落とし穴:動的に生成・削除される図形の名前衝突を防ぐユニーク名管理術 – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:Shape.NameIDの落とし穴と動的図形のユニーク名管理術

レガシーな業務システムや自動化フロー図の裏側で、Microsoft VisioのVBAは今なお強烈な影響力を放っている。APIの仕様は数十年ほぼ不変でありながら、現代の複雑なシステム連携や大量の動的オブジェクト生成においては、設計思想の古さがそのまま致命的なバグへと直結する。

特に、現場のエンジニアが最も深くハマる罠が 「図形の名前(Name / NameID)」 の制御だ。

今回は、動的に図形を生成・削除するプロセスにおいて発生する名前衝突のメカニズムを解体し、プログラムから確実に追跡可能なユニーク名を維持するためのアーキテクチャを提示する。

1. Visioにおける「名前」の正体:Name、NameU、NameIDの決定的な違い

Visioのオブジェクトモデルにおいて、図形を特定するためのプロパティは混同しやすい。まずは基本構造の正確な理解から始める必要がある。

  • `Name`: ローカライズされた図形名(例: `”プロセス”` や `”Sheet.1″`)。UI上の表示や言語環境に依存するため、プログラムのキーとして使うのは自殺行為である。
  • `NameU` (Universal Name): 言語に依存しない内部名(例: `”Process”` や `”Sheet.1″`)。マスター図形の参照などには必須だが、同一ページ内で一意性が保証されるのは「生成された瞬間」のみである。
  • `NameID`: セル数式やShapeSheet内で使用される一意の識別子(例: `”Sheet.1″`)。

開発者が陥る最初の罠:「Sheet.N」の循環と再利用

Visioの仕様として、ページ上で図形が削除されると、その名前に使われていたインデックス(番号)は解放され、次に生成される図形に再割り当てされる。

1. [BoxA] 生成 -> NameID: “Sheet.1”
2. [BoxB] 生成 -> NameID: “Sheet.2”
3. [BoxA] 削除 -> “Sheet.1” が空き番になる
4. [BoxC] 生成 -> NameID: “Sheet.1” が再割り当てされる!

この仕様を無視して `Shape.NameID = “Sheet.1″` のような一時的な参照を保持し続けると、意図しない別の図形を操作・上書きし、最悪の場合はサイレントエラーやVBAの実行時エラー(`#N/A` や `-2147352567 (80020009)`)を引き起こす。

2. 大量生成・削除環境における名前衝突のメカニズム

システム連携やシミュレーションツールなどで、外部DBやAPIから取得した数千件のデータを基に動的に図形を配置・更新するアーキテクチャを組む場合、以下の問題が必ず顕在化する。

1. インデックスの枯渇と競合: 頻繁な生成・削除の繰り返しにより、Visio内部のIDカウンタとVBA側の参照テーブルが乖離する。
2. メモリリークとCOMオブジェクトの残骸: 削除された図形への参照がメモリ上に残り続け、Visioプロセス全体の肥大化(Bloat)を招く。
3. トランザクションの欠如: 途中でエラーが発生した際、中途半端に名前が衝突した図形が残り、リカバリーが不可能になる。

これに対抗するには、Visioのデフォルトの命名規則に依存せず、アプリケーション層で完全に制御された独自のユニークIDをShapeSheetの「User定義セクション」に焼き付けるアプローチが唯一にして最大の防御策となる。

3. 実装コード:カスタムユニーク名管理とセーフティな動的生成

以下に、実務の現場で即座に活用できる堅牢なVBAコードを示す。ここでは、Visioのデフォルト名に頼らず、UUID(または連番+タイムスタンプ)をカスタムセルに埋め込み、完全に安全なオブジェクト追跡を行うフレームワークを構築している。

Option Explicit

‘ ==============================================================================
‘ 概要: 動的図形の安全な生成とカスタムユニーク名による追跡管理
‘ アーキテクトノート: デフォルトのNameIDに依存せず、User.CustomIDで一意性を担保する
‘ ==============================================================================

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

‘ トランザクションとパフォーマンスの最適化
Application.ScreenUpdating = False
Application.EventEnabled = False

On Error GoTo ErrorHandler

Dim i As Long
For i = 1 to 5
‘ 1. 図形の安全な生成(マスター図形を使用する前提)
Dim vsoMaster As Visio.Master
Set vsoMaster = Visio.Documents.Item(“basic_u.vssx”).Masters.ItemBypName(“Process”)

Dim vsoShape As Visio.Shape
Set vsoShape = vsoPage.Drop(vsoMaster, i 2.0, i 1.5)

‘ 2. カスタムユニークIDの付与(命名規則: “SYS_” & Timestamp & “_” & Index)
Dim uniqueIdentifier As String
uniqueIdentifier = “SYS_” & Format(Now, “yyyymmddhhmmss”) & “_” & CStr(i) & “_” & CreateGuidStub()

Call SetCustomShapeID(vsoShape, uniqueIdentifier)

‘ 3. テキストの設定
vsoShape.Text = “プロセス ” & i

‘ 4. 明示的なオブジェクト解放(メモリ最適化)
Set vsoShape = Nothing
Set vsoMaster = Nothing
Next i

MsgBox “図形の動的生成とユニーク名管理が完了しました。”, vbInformation

CleanUp:
Application.ScreenUpdating = True
Application.EventEnabled = True
Exit Sub

ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub

‘ ==============================================================================
‘ ShapeSheetのUser定義セルにカスタムIDを書き込む
‘ ==============================================================================
Private Sub SetCustomShapeID(ByVal vsoShape As Visio.Shape, ByVal id As String)
Const userCellName As String = “User.CustomUniqueID”
Dim vsoCell As Visio.Cell

On Error Resume Next
Set vsoCell = vsoShape.CellsU(userCellName)
If vsoCell Is Nothing Then
‘ セルが存在しない場合は追加
Set vsoCell = vsoShape.AddNamedRow(visSectionUser, “CustomUniqueID”, visTagDefault)
End If
On Error GoTo 0

‘ 文字列として値を格納(シングルクォーテーションで囲む必要がある)
vsoCell.FormulaU = “””” & id & “”””
End Sub

‘ ==============================================================================
‘ カスタムIDから図形を確実に逆引き・取得する(名前衝突を完全回避)
‘ ==============================================================================
Public Function FindShapeByCustomID(ByVal vsoPage As Visio.Page, ByVal targetID As String) As Visio.Shape
Dim vsoShape As Visio.Shape
Dim vsoCell As Visio.Cell
Dim foundShape As Visio.Shape
Set foundShape = Nothing

Dim i As Long
For i = vsoPage.Shapes.Count To 1 Step -1
Set vsoShape = vsoPage.Shapes(i)

‘ User.CustomUniqueIDセルの存在チェック
On Error Resume Next
Set vsoCell = vsoShape.CellsU(“User.CustomUniqueID”)
If Err.Number = 0 And Not vsoCell Is Nothing Then
‘ 結果からダブルクォーテーションを除去して比較
Dim currentVal As String
currentVal = Replace(vsoCell.ResultStr(visNoUnits), “”””, “”)

If currentVal = targetID Then
Set foundShape = vsoShape
Set vsoCell = Nothing
Set vsoShape = Nothing
Exit For
End If
End If
On Error GoTo 0
Set vsoCell = Nothing
Set vsoShape = Nothing
Next i

Set FindShapeByCustomID = foundShape
End Function

‘ ==============================================================================
‘ 簡易的なユニーク文字列生成スタブ(環境に応じてAPI等に変更可能)
‘ ==============================================================================
Private Function CreateGuidStub() As String
Randomize
CreateGuidStub = Hex(Int((99999 – 10000 + 1) Rnd + 10000))
End Function

4. チーフアーキテクトからの実践的提言

1. 画面描画とイベントの完全遮断 (`ScreenUpdating / EventEnabled`):
数千個の図形をループで操作する場合、これらを切断しない実行はCPUを不必要に焼き、Visioの描画エンジンに深刻な負荷をかける。バルク処理の基本中の基本である。
2. コレクション逆順ループの徹底:
図形の削除や動的走査を行う際、`For i = 1 To Shapes.Count` を使うとインデックスズレを起こす。削除時は必ず `Shapes.Count To 1 Step -1` のデクリメント方向で回せ。
3. COMオブジェクトの明示的解放:
VBAのガベージコレクションを信用するな。ローカル変数として宣言した `Visio.Shape` や `Visio.Cell` は、ループの末尾やプロシージャの脱出時に必ず `Set xxx = Nothing` で解放し、メモリリークの芽を摘み取れ。

Visio VBAは古い技術ではない。インフラストラクチャやAPI連携の歯車として、その内部仕様を完全に掌握した者にとっては、今なお極めて強力な自動化兵器である。妥協のない設計で、堅牢なシステム構築を貫いてほしい。

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