Visio VBAを掌握する極限の知見:`Shape.ItemFromIDExists` で実現する「死んだID」の恐怖からの解放
開発プロジェクトのリーダーである私たちが、Visio VBAによる自動化ツールで最も直面したくない瞬間は何だろうか。それは、大規模な図面データを処理している最中、突如として画面に突きつけられる「実行時エラー ‘-2008’: 指定された識別子の図形が存在しません」という非情なメッセージだ。
図形ID(`Shape.ID`)を配列やコレクションに保持し、後続の処理でそれを再参照して一括操作する――自動化スクリプトとしてはごく一般的な設計である。しかし、処理の途中でユーザーが手動で図形を削除したり、別のルーチンが古いIDを参照し続けたりした瞬間、容赦なくコードはクラッシュする。
「エラーハンドラーで`On Error Resume Next`を仕込んでおけばいいだろう」――そんな安易な設計をしているうちは、アマチュアの域を出ない。エラーを握り潰すコードはバグの温床であり、予期せぬデータ破損を引き起こす。
今回は、Visioのオブジェクトモデルの暗部を知り尽くしたプロフェッショナルへ向けて、`Page.ItemFromIDExists` メソッドを用いた「絶対にクラッシュしない堅牢な図形参照パターン」を叩き込む。
—
なぜ従来のID参照は「地雷原」なのか?
Visioの `Page.Shapes` コレクションや `Document` は、内部的に図形のライフサイクルを管理している。ここで重要な事実を一つ。「Visioの図形IDは、一度発行されるとページ内では再利用されない(インクリメントされ続ける)」。
一見すると安全に思えるこの仕様だが、ここに大きな罠がある。
IDという「数値」は永続的であっても、そのIDを持つオブジェクト(`Shape`実体)がメモリ上に存在し続ける保証はどこにもない。
愚かな実装例(アンチパターン)
‘ 【絶対に見習ってはならない危険なコード】
Dim targetShape As Visio.Shape
Dim targetID As Long
targetID = 142 ‘ 何らかの処理で保持していたID
‘ 存在確認なしに直接アクセス
Set targetShape = ActivePage.Shapes.ItemFromID(targetID)
‘ ↑もしID:142の図形が削除されていたら、ここで即座に実行時エラーでクラッシュ!
targetShape.Text = “更新完了”
このコードは、現場の運用テストではうまく動く。しかし、実務でユーザーが勝手に図形を一つ消した瞬間に発動する「時限爆弾」となる。
—
救世主 `ItemFromIDExists` による完全防御
この問題を根底から解決するのが、Visio 2010以降で導入された `Page.ItemFromIDExists` メソッドだ。
このメソッドの真価は、例外を発生させずに、指定したIDの図形がページ内に生存しているかをブール値(True/False)で返しつつ、同時に生存していればオブジェクトを安全に取得できる点にある(VBAの `ByRef` を利用した洗練されたインターフェースだ)。
シグネチャの構造
Dim shp As Visio.Shape
Dim exists As Boolean
exists = ActivePage.ItemFromIDExists(ID, shp)
- 戻り値:図形が存在すれば `True`、していなければ `False`
- 引数 `shp`:存在する場合は該当する `Shape` オブジェクトが格納され、存在しない場合は `Nothing` になる。
これを使えば、エラートラップに頼らない、極めてモダンでクリーンなロジックを構築できる。
—
【実践】プロダクションコード:堅牢な一括プロパティ更新エンジン
実際の業務システム(データベースや外部ExcelからのインポートデータとVisio図面を同期するツール)を想定した、実用に耐えうるモジュールを提供する。
このコードは、外部から渡された「IDのリスト」を元に図形を安全に検索し、存在するものだけに対してスタイルの適用とテキスト更新を行う。
Option Explicit
Public Sub ExecuteRobustShapeUpdate()
Dim wsPage As Visio.Page
Set wsPage = ActivePage
‘ 例:外部DBや設定ファイルから取得した「更新対象のIDリスト」を想定
Dim targetIDs() As Long
ReDim targetIDs(2)
targetIDs(0) = 105
targetIDs(1) = 999 ‘ 存在しないかもしれないダミーID
targetIDs(2) = 210
Dim i As Long
Dim processedCount As Long
processedCount = 0
‘ トランザクション的な処理の開始(画面描画を停止して高速化&安定化)
Application.ScreenUpdating = False
On Error GoTo ErrorHandler
For i = LBound(targetIDs) To UBound(targetIDs)
Dim targetID As Long
targetID = targetIDs(i)
Dim targetShp As Visio.Shape
‘ 【核心】ItemFromIDExistsによる安全な生存確認とオブジェクト取得
If wsPage.ItemFromIDExists(targetID, targetShp) Then
‘ — 生存確認成功:安全に操作を実行 —
Call ApplyBusinessLogic(targetShp)
processedCount = processedCount + 1
Else
‘ — 既に削除されている場合のフォールバック処理 —
‘ ログ出力やイミディエイトウインドウへの記録など
Debug.Print “警告: ID ” & targetID & ” の図形は既に削除されているためスキップしました。”
End If
Next i
CleanUp:
Application.ScreenUpdating = True
MsgBox “処理が完了しました。更新成功件数: ” & processedCount, vbInformation, “同期完了”
Exit Sub
ErrorHandler:
Application.ScreenUpdating = True
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Sub
‘ 実際のビジネスロジックをカプセル化したプロシージャ
Private Sub ApplyBusinessLogic(ByRef shp As Visio.Shape)
‘ 参照が確実に保証された図形に対する安全な操作
shp.Cells(“LineColor”).FormulaU = “RGB(0, 120, 215)”
shp.Cells(“LineWeight”).FormulaU = “2 pt”
‘ メタデータの付与(カスタムプロパティの書き込みなど)
‘ ※シェイプシートの存在確認も行えばさらに堅牢になる
End Sub
—
アーキテクトからの設計上のアドバイス
1. `Application.ScreenUpdating = False` との組み合わせ
大量の図形をループで処理する際、画面描画を抑制することでパフォーマンスが劇的に向上する。しかし、エラー発生時に `ScreenUpdating` が `False` のまま取り残されると、VisioのUIがフリーズしたような状態になりユーザーが混乱する。必ず上記のコードのように `On Error GoTo` を用いたクリーンアップ処理(確実な `True` への復帰)をセットで実装すること。
2. データベース連携時の注意点
外部DB(SQL ServerやAccessなど)の主キーとVisioの `Shape.ID` を紐づけて管理するアーキテクチャは非常に強力だが、ユーザーがVisio上で図形を「コピー&ペースト」すると、新しいIDが採番されるという仕様トラップがある。
DB側と同期を取る際は、IDだけでなく、シェイプの `NameID` やユーザークロスリファレンス(Airtableやカスタムプロパティに埋め込んだUUID等)との併用を検討すべきだ。その場合でも、ローカルな図面内での一時的な生死確認には、この `ItemFromIDExists` が圧倒的なパフォーマンスを発揮する。
結び
プロとアマを分ける境界線は、「正常系コードをいかに美しく書くか」ではなく、「異常系・破壊的ユーザー操作に対して、いかにシステムが崩壊しないか」の作り込みにある。
`ItemFromIDExists` は、Visio VBA開発者にとっての必須の防壁である。このパターンをあなたの標準ライブラリに組み込み、明日からの開発現場から「図形が存在しませんエラー」を完全に根絶してほしい。
