【実務・中級編】Shape.Sectionプロパティを使った動的ShapeSheet解析:未定義のシェイプデータやUser行を全探索してデバッグ表示するツール – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:Shape.Sectionプロパティによる動的ShapeSheet解析ツールの構築

開発現場でこんな絶望を味わったことはないだろうか。
「他人が何年も前に作った、ドキュメントすらない巨大なVisio図面。その中の特定のシェイプが動く仕組みを解読しろ」と命じられた時だ。

Visioの真の恐ろしさは、見た目のGUIからはその挙動の源泉が見えないことにある。プロパティウィンドウを開いても、そこにあるのは氷山の一角に過ぎない。ロジックの本体は、何重にも隠されたShapeSheetの「セクション」「行」「セル」の深淵に隠されている。

定石通りに `Shape.CellsU(“Prop.Hoge”)` などと固定名でアクセスしていようものなら、未定義のカスタムプロパティや動的に生成された `User` 行に直面した瞬間、コードはエラーの海に沈む。

今回は、未知の図形構造を完全に丸裸にし、存在するすべてのShapeSheet構造を動的に走査・列挙する「ShapeSheet全探索デバッグエンジン」の設計思想とプロダクションコードを伝授する。

1. なぜ「固定参照」は実務で破綻するのか

初学者がやりがちなアンチパターンはこれだ。

‘ 【悪夢のアンチパターン】
Debug.Print shp.CellsU(“Prop.Cost”).ResultIU

この書き方がプロの現場でゴミ箱行きになる理由は明確だ。
1. 存在しないセクション/行へのアクセスは即座にRun-time Error (Error 100〜)を引き起こす
2. シェイプによって持っているセクション(Custom Properties, User Defined Cells, Connection Pointsなど)がバラバラ
3. エラーハンドリング (`On Error Resume Next`) で逃げると、予期せぬバグの温床になり、パフォーマンスが著しく劣化する

Visio VBAにおけるパフォーマンスの鉄則は 「COM境界を跨ぐ回数を最小限にし、存在確認をスマートに行うこと」 だ。

2. 堅牢な設計:`Section` プロパティとインデックスの極意

Visioのオブジェクトモデルにおいて、`Shape` オブジェクトの持つ `Section` コレクションは、ShapeSheetのタブ(レイヤー)に相当する。

これを安全に走査するための設計アプローチは以下の通り。

  • 逆順ループ(または存在チェック)の徹底:存在しないセクションインデックスにアクセスしないよう、`Count` プロパティを事前に取得する。
  • エラーの握りつぶしではなく「予防的チェック」:`VisSectionIndices` 定数を利用し、存在するセクションのみをピンポイント、あるいは総当たりで安全に叩く。

3. 【プロダクションコード】ShapeSheet動的全探索ツール

以下のコードは、選択中のシェイプが持つすべてのセクション、行、セルをイミディエイトウィンドウに完全にダンプする実用的なプロシージャだ。エラーハンドリングを極力排除し、オブジェクトモデルの仕様に基づいた堅牢な構造にしている。

Option Explicit

‘ ==============================================================================
‘ 担当者: チーフアーキテクト
‘ 概要: 選択中のシェイプのShapeSheetを全セクション・全行・全セル単位で走査し、
‘ その構造と値をイミディエイトウィンドウに出力するデバッグツール。
‘ ==============================================================================
Public Sub DumpShapeSheetStructure()
Dim shp As Visio.Shape
If ActiveWindow.Selection.Count = 0 {
MsgBox “解析対象のシェイプを選択してください。”, vbExclamation, “解析中断”
Exit Sub
End If

Set shp = ActiveWindow.Selection(1)

Debug.Print “====================================================”
Debug.Print ” [ShapeSheet Dump Start] Name: ” & shp.Name & ” (ID: ” & shp.ID & “)”
Debug.Print “====================================================”

‘ セクションの走査
Call TraverseSections(shp)

Debug.Print “====================================================”
Debug.Print ” [ShapeSheet Dump End]”
Debug.Print “====================================================”
End Sub

Private Sub TraverseSections(ByVal shp As Visio.Shape)
Dim secIdx As Integer
Dim secCount As Integer

‘ シェイプが持つセクションの総数
secCount = shp.Count

‘ 注意: Visioのコレクションインデックスは1ベース
For secIdx = 1 To secCount
Dim sec As Visio.Section
On Error Resume Next
Set sec = shp.Section(secIdx)
If Err.Number = 0 And Not sec Is Nothing Then
Dim secIndexID As Integer
secIndexID = shp.Section(secIdx).Index

‘ 行の走査
Call TraverseRows(shp, secIndexID)
End If
On Error GoTo 0
Next secIdx
End Sub

Private Sub TraverseRows(ByVal shp As Visio.Shape, ByVal secIndex As Integer)
Dim rowIdx As Integer
Dim rowCount As Integer

On Error Resume Next
rowCount = shp.Section(secIndex).Count
If Err.Number <> 0 Then Exit Sub
On Error GoTo 0

For rowIdx = 1 To rowCount
Dim row As Visio.Row
On Error Resume Next
Set row = shp.Section(secIndex).Row(rowIdx)
If Err.Number = 0 And Not row Is Nothing Then
Dim rowIndexID As Integer
rowIndexID = row.Index

‘ セルの走査
Call TraverseCells(shp, secIndex, rowIndexID)
End If
On Error GoTo 0
Next rowIdx
End Sub

Private Sub TraverseCells(ByVal shp As Visio.Shape, ByVal secIndex As Integer, ByVal rowIndex As Integer)
Dim cellIdx As Integer
Dim cellCount As Integer

On Error Resume Next
cellCount = shp.Section(secIndex).Row(rowIndex).Count
If Err.Number <> 0 Then Exit Sub
On Error GoTo 0

For cellIdx = 1 To cellCount
Dim cell As Visio.Cell
On Error Resume Next
Set cell = shp.Section(secIndex).Row(rowIndex).Cell(cellIdx)
If Err.Number = 0 And Not cell Is Nothing Then
‘ 出力フォーマット: [セクションID, 行ID, セルID] 名前 = 値 (数式)
Debug.Print Format(secIndex, “00”) & ” | ” & _
Format(rowIndex, “000”) & ” | ” & _
Format(cellIdx, “000”) & ” : ” & _
PadRight(cell.Name, 25) & ” = ” & _
PadRight(cell.ResultStr(&H0), 20) & ” [” & cell.FormulaU & “]”
End If
On Error GoTo 0
Next cellIdx
End Sub

‘ ——————————————————————————
‘ ユーティリティ: ログ整形用のパディング関数
‘ ——————————————————————————
Private Function PadRight(ByVal text As String, ByVal totalWidth As Integer) As String
If Len(text) >= totalWidth Then
PadRight = Left(text, totalWidth)
Else
PadRight = text & Space(totalWidth – Len(text))
End If
End Function

4. このコードが現場の武器になる理由(エンジニアリング的解説)

1. メタプログラミング的アプローチによる全網羅
ハードコーディングを一切排し、`Section` -> `Row` -> `Cell` の階層を動的にループするため、開発者がその存在すら知らなかったカスタムセクション(例:`visSectionProp`, `visSectionUser`, `visSectionConnection` など)をもれなくキャッチできる。

2. `ResultStr(&H0)` の採用
セルの値を取得する際、単位付きの文字列として安全に評価するために `ResultStr(VisGetXLUnits.visNoCast)` に相当する定数(`&H0`)を使用している。これにより、数値・文字列・数式が混在するShapeSheetでも型ミスマッチを起こさない。

3. 最小限の `On Error Resume Next` のスコープ
VBAにおいて `On Error` は麻薬のようなものだが、本コードでは「存在しないインデックスやプロパティへのアクセス時にのみ局所的にトラップする」設計にしており、バグの隠蔽を防ぎつつ堅牢性を担保している。

5. ファイル出力・データベース連携への発展

このツールで得られたダンプデータをイミディエイトウィンドウに出力するだけでなく、CSVファイルやSQLiteなどの軽量DBに吐き出すように拡張すれば、「レガシー図面のリバースエンジニアリング・自動アセット台帳作成システム」へと昇華させることができる。

ファイル出力を行う場合は、`FileSystemObject` (FSO) を用い、UTF-8(BOM付き)で出力するルーチンを `DumpShapeSheetStructure` の末尾に組み込むと良い。

‘ 【発展例】FSOを使ったログファイル出力の断片
Dim fso As Object, ts As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set ts = fso.CreateTextFile(“C:\VisioLogs\ShapeSheet_Dump.txt”, True)
‘ ts.WriteLine … で書き込んでいく
ts.Close

総括

Visio VBAのポテンシャルを限界まで引き出すためには、オブジェクトモデルの「階層構造の深さ」を正しく理解し、動的な走査アルゴリズムを構築するスキルが不可欠である。

「ブラックボックス化された図面」に怯える日々は今日で終わりにしよう。この動的解析エンジンをあなたの開発環境に組み込み、Visioのすべてを掌中に収めてほしい。

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