【Visio VBA極限解説】ShapeIDとName・NameUの深淵:なぜあなたのマクロは図形を見失うのか?
開発現場でよく耳にする悲鳴がある。
「昨日まで動いていたマクロが、図形をコピーしたら動かなくなった」
「担当者が図形の名前を変えた途端、データ連携ツールが沈黙した」
Visio VBAにおけるオブジェクト操作の成否は、突き詰めれば「Shapeをいかに正確に特定するか」のワンポイントにかかっている。Application、Document、Pageを正しく階層辿りできたとしても、肝心のShapeの特定で迷子になっていては、業務自動化エンジニアの名が廃るというものだ。
今回は、Visioの心臓部であるShapeオブジェクトの識別子、「ShapeID」「Name」「NameU」の本質的な違いを解き明かし、荒波の現場でも微動だにしない堅牢な図形取得ロジックの構築法を伝授する。
—
1. 三者の正体を暴く:ID、Name、NameUの生存戦略
VisioのShapeオブジェクトを識別するプロパティには、主に以下の3つが存在する。これらは似て非なるものであり、ライフサイクル(生存期間)やスコープが全く異なる。
| プロパティ | 型 | 一意性 | 可変性 | 主な用途・特徴 |
| :— | :— | :— | :— | :— |
| `ID` | `Long` | ページ内で一意 | 変動する(削除・追加で変化) | 高速な内部インデックス。同一ページ内でのループ処理や一時的な特定に最適。 |
| `Name` | `String` | ページ内で一意 | 変更可能(UI上で変更可能) | ユーザーインターフェース(UI)上の表示名。言語依存。 |
| `NameU` | `String` | ページ内で一意 | 変更可能(コード等で変更可能) | Universal Name(言語非依存のマスター名)。多言語環境や堅牢な識別用。 |
ShapeIDの罠:刹那の高速性
`ActivePage.Shapes.ItemFromID(id)` や `Page.Shapes(id)` で使われる `ID` は、Visioエンジンが内部管理している数値だ。
非常に軽量で高速にアクセスできるが、「図形の削除と新規作成」のサイクルによって採番が狂う。例えば、ID=5の図形を削除し、新しい図形を作ると、次の図形がID=5を引き継ぐか、あるいは全く別の番号になる。したがって、「永続的な識別子」としてコードにハードコーディングしては絶対にいけない。
Name と NameU の決定的な違い
Visioの図形には「プロセス」「四角形」といったマスターシェイプが存在する。
- `Name`: 日本語環境であれば「プロセス.12」のように、OSやVisioの言語設定に依存するローカライズされた名前。
- `NameU`: 「Process.12」のように、内部的に言語非依存(Universal)で保持される名前。
もしあなたの開発したVBAが、英語圏のクライアントや多言語環境のPCで動く可能性があるなら、`Name` での条件分岐は地雷を踏むようなものだ。堅牢性を担保したいなら、識別には必ず `NameU` を使うべきである。
—
2. 現場で使える「堅牢な図形特定パターン」の設計思想
実務において、特定の図形(例えば、データベースから読み込んだIDを紐付けたい図形、あるいは特定のステータスを持つ図形)を確実に取得するにはどうすべきか。
ここで、やってはいけないアンチパターンと、プロダクションコードで採用すべきベストプラクティスを提示する。
❌ アンチパターンの例(なぜダメなのか)
‘ 【最悪の例】IDや表示名(Name)を直接指定する
Dim shp As Visio.Shape
Set shp = ActivePage.Shapes(“プロセス.1”) ‘ 言語依存、ユーザーが名前を変えると即死
Set shp = ActivePage.Shapes(3) ‘ IDはレイアウト変更で簡単にずれる
⭕ プロダクションコードの設計思想
1. カスタムプロパティ(Propセクション)や UserDefinedCells を活用する
図形の「名前」や「ID」に頼るのではなく、図形が持つメタデータ(例: `Prop.AssetID`)に一意の業務IDを書き込んでおき、それを走査して取得する。
2. NameUによるプレフィックス検索
マスターから生成された際のベース名(NameU)をチェックし、特定の形状を判定する。
—
3. 【コピペOK】実務対応・堅牢な図形取得ファンクション
以下のコードは、実際の業務システム連携(ExcelやDBからのインポート等)を想定し、「図形のNameU」および「カスタムプロパティ」を安全に走査して特定のShapeオブジェクトを確実に取得する実践的なモジュールである。
Option Explicit
‘ ==============================================================================
‘ 概要: 指定したページ内から、カスタムプロパティ(業務ID)または NameU を基に
‘ 確実に対象のShapeオブジェクトを取得する堅牢な関数
‘ ==============================================================================
Public Sub ExecuteRobustShapeRetrieval()
Dim targetShape As Visio.Shape
‘ 例1: カスタムプロパティ(例: Prop.EquipmentID)の値で図形を特定する(推奨)
‘ ※ Visioの図形データウィンドウで設定したプロパティ名を指定
Set targetShape = FindShapeByCustomProperty(ActivePage, “Prop.EquipmentID”, “EQ-9981”)
If Not targetShape Is Nothing Then
MsgBox “図形を発見しました!” & vbCrLf & _
“Shape ID: ” & targetShape.ID & vbCrLf & _
“NameU: ” & targetShape.NameU, vbInformation, “探索成功”
アクション例(色を赤に変える)
targetShape.Cells(“FillForegnd”).FormulaU = “RGB(255, 0, 0)”
Else
MsgBox “対象の図形が見つかりませんでした。”, vbExclamation, “探索失敗”
End If
‘ 例2: NameUのプレフィックスとテキスト内容で図形を特定する
Set targetShape = FindShapeByNameUAndText(ActivePage, “Process”, “承認処理”)
If Not targetShape Is Nothing Then
‘ 処理…
End If
End Sub
‘ ——————————————————————————
‘ 内部関数: カスタムプロパティの値が一致するShapeを返す
‘ ——————————————————————————
Private Function FindShapeByCustomProperty(targetPage As Visio.Page, propName As String, targetValue As String) As Visio.Shape
Dim shp As Visio.Shape
Dim propCellName As String
On Error GoTo ErrorHandler
For Each shp in targetPage.Shapes
‘ 図形が指定されたプロパティセクションを持っているか確認
If shp.CellExists(propName, visExistsLocally) Then
‘ プロパティの値(文字列)を取得し、トリムして比較
Dim actualValue As String
actualValue = Trim(shp.Cells(propName).ResultStr(&H20)) ‘ visNoCast
If actualValue = targetValue Then
Set FindShapeByCustomProperty = shp
Exit Function
End If
End If
Next shp
Set FindShapeByCustomProperty = Nothing
Exit Function
ErrorHandler:
‘ 予期せぬセルエラー等をスルーして安全に続行
Set FindShapeByCustomProperty = Nothing
End Function
‘ ——————————————————————————
‘ 内部関数: NameU(部分一致)とシェイプテキストの双方から図形を特定する
‘ ——————————————————————————
Private Function FindShapeByNameUAndText(targetPage As Visio.Page, nameUPattern As String, shapeText As String) As Visio.Shape
Dim shp As Visio.Shape
For Each shp in targetPage.Shapes
‘ NameUに指定文字列が含まれ、かつテキストが一致するもの
If InStr(1, shp.NameU, nameUPattern, vbTextCompare) > 0 Then
‘ Textプロパティは末尾に改行が入ることがあるためTrimする
Dim currentText As String
currentText = Trim(Replace(shp.Text, vbCrLf, “”))
currentText = Trim(Replace(currentText, vbCr, “”))
currentText = Trim(Replace(currentText, vbLf, “”))
If currentText = shapeText Then
Set FindShapeByNameUAndText = shp
Exit Function
End If
End If
Next shp
Set FindShapeByNameUAndText = Nothing
End Function
—
4. チーフアーキテクトからの実務アドバイス:パフォーマンスと保守の極意
1. グループ化された図形(Container / Group)の罠
上記のコードは `ActivePage.Shapes` をループしているが、これが「グループ化された中の子図形」である場合、トップレベルの `Shapes` コレクションには含まれない。階層構造を持つ図形を操作する場合は、`IsGroup` プロパティを判定し、再帰処理(Recursive)で子図形を掘り下げる設計が不可欠となる。
2. エラーハンドリングの徹底
Visio VBAは、存在しないセル名やIDにアクセスした瞬間に実行時エラーで強制終了する。`CellExists` メソッドや `On Error Resume Next` (限定的なスコープでの使用)を適切に組み合わせ、現場の運用でマクロがストップしない堅牢性を確保すること。
Shapeの識別という一見地味なテーマだが、ここを疎かにすれば巨大な自動化ツールの信頼性は一瞬で崩れ去る。IDの利便性とNameUの普遍性を正しく理解し、明日の開発現場で「壊れないVBA」を実装してほしい。
