Visio VBAを「堅牢なプロダクト」へ昇華させる:幽霊シェイプと消えたプロパティを封じ込める例外処理の極意
Visio VBAの世界へようこそ。私は数多の巨大なシステム図面、プラント設計、ネットワークトポロジの自動生成ツールを構築してきたチーフアーキテクトだ。
君がもし、「`On Error Resume Next`を冒頭に書いておけば安心だ」などと考えているなら、今すぐその考えを捨ててほしい。それはプログラミングではなく、単なる「現実逃避」だ。Visioというアプリケーションは、ExcelやWordとは比較にならないほど「オブジェクトの動的変化」が激しい。ユーザーがマクロ実行中にシェイプを削除する、あるいは独自のシェイプデータを消去する……こうした事態は日常茶飯事だ。
本稿では、存在しないシェイプやプロパティへのアクセスによってツールをクラッシュさせないための、「防御的プログラミング」の真髄を伝授する。
—
1. なぜVisio VBAは「落ちる」のか?:ID参照の罠
Visio開発者が最初にはまる罠、それが「シェイプIDへの過信」だ。
Visioの各シェイプには一意の`ID`が割り振られる。しかし、このIDは絶対ではない。ユーザーによる削除、Undo/Redo、あるいはページのコピーによって、昨日まで存在した`ID: 101`は、今日はもう存在しない「幽霊」と化す。
失敗する典型例
‘ 危険なコード:ID 101が存在することを前提としている
Set vsoShape = vsoPage.Shapes.ItemFromID(101) ‘ ここで実行時エラー「無効な識別子です」が発生
vsoShape.Text = “Update”
プロの設計では、「オブジェクトが存在しない可能性」をデフォルトとして扱う。
—
2. 存在確認の鉄則:`CellExists` と `ItemFromID` の安全なハンドリング
プロパティ(シェイプデータやカスタムプロパティ)にアクセスする際、最も強力な武器は `CellExists` プロパティだ。多くの開発者が `On Error` で無理やり突破しようとするが、それは計算コストが高く、デバッグを困難にする。
シェイプデータ(Prop.XXX)への安全なアクセス
Visioのシェイプデータは、セル(Cell)として管理されている。存在しないセルを `Cells(“Prop.Status”)` のように指定すると即座にエラーになる。これを防ぐには以下のロジックが不可欠だ。
”’
”’
Public Sub SafeSetShapeData(ByRef vsoShape As Visio.Shape, ByVal propName As String, ByVal value As String)
‘ “Prop.” プレフィックスの補完
Dim fullPropName As String
fullPropName = IIf(Left(propName, 5) = “Prop.”, propName, “Prop.” & propName)
‘ visExistsAnywhere (1) を使用して存在確認
If vsoShape.CellExists(fullPropName, visExistsAnywhere) <> 0 Then
vsoShape.Cells(fullPropName).FormulaU = Chr(34) & value & Chr(34)
Else
‘ ここでログを出力するか、必要に応じて行を追加する設計にする
Debug.Print “Warning: ” & fullPropName & ” does not exist in Shape ID ” & vsoShape.ID
End If
End Sub
—
3. 実戦用:堅牢なシェイプ取得ラッパー
次に、IDからシェイプを取得する際の「決定版」とも言える関数を紹介しよう。この関数は、エラーを呼び出し元に波及させず、`Nothing` を返すことで後続の処理に判断を委ねる。
”’
”’
Public Function GetShapeByIdSafe(ByRef vsoPage As Visio.Page, ByVal shapeID As Integer) As Visio.Shape
On Error Resume Next ‘ このスコープ内のみ一時的にエラーを無視
Dim targetShape As Visio.Shape
Set targetShape = vsoPage.Shapes.ItemFromID(shapeID)
On Error GoTo 0 ‘ エラーハンドリングを即座にリセット
Set GetShapeByIdSafe = targetShape
End Function
—
4. プロダクション・グレードの実装例:データベース連携の堅牢化
実務では、Excelやデータベースのリストを元にVisio上のシェイプを更新するツールが多い。ここでは、「リストにはあるが、図面上から消されたシェイプ」を検知しながら処理を継続する、保守性の高いコード例を示す。
Public Sub SyncDiagramWithDatabase()
Dim vsoPage As Visio.Page: Set vsoPage = ActivePage
Dim shapeList As Variant
‘ 仮のデータ:ID 10, 25, 99 (99は削除済みと想定)
shapeList = Array(10, 25, 99)
Dim i As Long
Dim targetID As Integer
Dim vsoShape As Visio.Shape
For i = LBound(shapeList) To UBound(shapeList)
targetID = shapeList(i)
‘ 1. 安全に取得
Set vsoShape = GetShapeByIdSafe(vsoPage, targetID)
‘ 2. 存在チェック(ガード節による早期リターン)
If vsoShape Is Nothing Then
Debug.Print “Error: Shape ID ” & targetID & ” was not found. Skipping…”
GoTo ContinueLoop
End If
‘ 3. 属性更新(ここでも安全策を講じる)
UpdateShapeStatus vsoShape, “完了”
ContinueLoop:
Next i
End Sub
Private Sub UpdateShapeStatus(ByRef shp As Visio.Shape, ByVal status As String)
‘ セルの存在確認を行いつつ更新
If shp.CellExists(“Prop.Status”, visExistsAnywhere) Then
shp.Cells(“Prop.Status”).FormulaU = Chr(34) & status & Chr(34)
Else
‘ セルがない場合は新規作成するか、エラーログを出す
‘ 開発者の意図により設計が分かれるポイント
Debug.Print “Shape ID ” & shp.ID & ” has no ‘Status’ property.”
End If
End Sub
—
5. アーキテクトからの助言:ファイル・DB連携の注意点
Visio VBAで堅牢なツールを作るなら、以下の3つの設計指針を心に刻んでほしい。
1. 「IDは揮発性」だと理解せよ:
外部データベースと連携する場合、Visioの内部ID(ShapeID)だけをキーにするのは危険だ。可能な限り、シェイプデータの特定行に「独自のユニークキー(GUID等)」を持たせ、それを使って検索するロジックを組むべきだ。
2. `visExistsAnywhere` の活用:
`CellExists` には、シェイプそのものにあるか、マスターシェイプにあるかを判定するフラグがある。`visExistsAnywhere` を使うことで、継承されたプロパティも安全にチェックできる。
3. ユーザー操作への耐性:
マクロ実行中にユーザーが「Ctrl+Z(元に戻す)」を押したり、シェイプを消したりする可能性を考慮せよ。重要な処理の前後では `Application.IsUndoingOrRedoing` をチェックするか、あるいは処理全体を `Application.BeginUndoScope` で包み、一貫性を保証せよ。
結論
エラーハンドリングとは、単にエラーメッセージを隠すことではない。「想定外の事態を想定内のフローに引き戻すこと」である。
今回紹介した `CellExists` による事前検知と、`On Error Resume Next` を極小スコープに限定したラッパー関数の組み合わせは、Visio VBA開発における「黄金律」だ。これを徹底するだけで、君の作成するツールの信頼性は劇的に向上し、深夜に鳴り響く「マクロが動かない」という電話をゼロに近づけることができるだろう。
コードを書くのではない。堅牢な構造を設計せよ。
