【実務・中級編】【上級】レガシー図面保守のためのShapeSheet全セクション走査:未知のプロパティ構造を解析するデバッグツール – Visio VBA解析バイブル

スポンサーリンク

【上級】レガシー図面保守のためのShapeSheet全セクション走査:未知のプロパティ構造を解析するデバッグツール

開発現場で最も恐ろしいもの何か。それは「前任者が遺した、ドキュメントすらないレガシー図面」だ。

何世代にもわたって継ぎ接ぎされたVisio図面は、もはや単なるビジュアルデータではない。その内部(ShapeSheet)には、蜘蛛の巣のように張り巡らされた数式、硬直化したカスタムプロパティ(Shape Data)、そして誰も意図しない挙動を引き起こす魔改造されたセクションが隠されている。

画面上をポチポチクリックしてインスペクターウィンドウを眺める? そんなアナログなデバッグ手法は、プロのエンジニアの仕事ではない。コードで殴り、全セクションを丸裸にする。今回は、Visio VBAの深淵に踏み込み、未知のプロパティ構造を完全にハックするための「ShapeSheet全セクション走査デバッグツール」の設計思想と実装を授けよう。

—

1. なぜ通常の走査ではレガシー図面を攻略できないのか?

Visioのオブジェクトモデルは一見するとシンプルだ。`Application` から `Document`、`Page`、そして `Shape` へと階層を降りていけば、すべての図形にアクセスできる。

しかし、ここに大きな罠がある。
初学者が陥る最大の過ちは、`Shape.Cells` や `Shape.CellsU` を使って、知っているプロパティ名(例: `Prop.Cost` や `Width`)を個別に取りにいこうとすることだ。

‘ 【アンチパターン】これでは未知のプロパティを発見できない
Dim shp As Visio.Shape
Set shp = ActivePage.Shapes(1)
Debug.Print shp.CellsU(“Prop.UnknownProperty”).FormulaU ‘ 存在しないプロパティを指定すると実行時エラーで即死する

レガシー図面において、プロパティの命名規則など存在しない。さらに、VisioのShapeSheetはセクション(Section)という概念を持ち、各セクションの中にロウ(Row)、さらにその中にセル(Cell)が多次元配列的に存在している。

真に堅牢な解析ツールを作るには、以下の要件を満たす必要がある。

1. エラーハンドリングの要塞化: 存在しないセクションやインデックスにアクセスしても沈黙しない(あるいは適切にスキップする)ロジック。
2. 定数(Constants)の完全網羅: Visioが持つ数十種類のセクションインデックス(`visSectionProp`, `visSectionCharacter` 等)を動的に、かつ網羅的にスキャンするアプローチ。
3. パフォーマンスの担保: 巨大な図面(数千個のシェイプ)を走査してもメモリリークやフリーズを起こさない最適化。

—

2. アーキテクチャ設計:セクション・ロウ・セルの3次元ループ

VisioのShapeSheet構造は、以下の階層構造を持っている。

  • Shape(図形)
  • Section(セクション): 例:カスタムプロパティ、スクラッチ、アクション等
  • Row(行): セクション内の個別のエントリ
  • Cell(セル): 具体的な値や数式

これをすべてコードで手撃みするのは不可能だ。したがって、Visioの型ライブラリが提供するセクション定数を配列として定義し、「存在するセクション $\rightarrow$ 存在するロウ $\rightarrow$ すべてのセル」の順で総当たり(Brute-force)するアルゴリズムを構築する。

—

3. 【プロダクションコード】ShapeSheet全セクション走査ツール

以下のコードは、選択されたシェイプ(またはページ全体のシェイプ)のShapeSheetを深掘りし、設定されているすべての数式と値をイミディエイトウィンドウ、またはCSV等の外部出力へ流し込むための基幹エンジンだ。

実務でそのままコピー&ペーストし、即座にプロジェクトに組み込めるよう設計している。

Option Explicit

‘ ==============================================================================
‘ módulo名: modShapeSheetInspector
‘ 概要: レガシー図面のShapeSheetを全セクション走査し、未知のプロパティを暴く
‘ ==============================================================================

Public Sub RunDeepInspection()
Dim targetShape As Visio.Shape
Dim docName As String

‘ ガード節: 図面が開かれているか
If ActiveDocument Is Nothing Then
MsgBox “アクティブなドキュメントが存在しません。”, vbCritical, “致命的エラー”
Exit Sub
End If

‘ ガード節: シェイプが選択されているか
If ActiveWindow.Selection.Count = 0 Then
MsgBox “解析対象のシェイプを1つ以上選択してください。”, vbExclamation, “選択エラー”
Exit Sub
End If

Debug.Print “=== ShapeSheet Deep Inspection Start ===”
Debug.Print “Timestamp: ” & Now

‘ 選択されたシェイプを順次走査(グループ化されたシェイプの再帰処理も考慮)
For Each targetShape In ActiveWindow.Selection
Call InspectShapeRecursive(targetShape, 0)
Next targetShape

Debug.Print “=== ShapeSheet Deep Inspection Completed ===”
MsgBox “解析が完了しました。イミディエイトウィンドウを確認してください。”, vbInformation, “完了”
End Sub

‘ ——————————————————————————
‘ 再帰的にシェイプ(グループ内含む)を走査するプロシージャ
‘ ——————————————————————————
Private Sub InspectShapeRecursive(shp As Visio.Shape, depth As Long)
Dim indent As String
indent = String(depth 2, ” “)

Debug.Print indent & “+- Shape Name: ” & shp.Name & ” (ID: ” & shp.ID & “, Type: ” & GetShapeTypeName(shp.Type) & “)”

‘ ShapeSheetの全セクションをスキャン
Call ScanAllSections(shp, indent & ” “)

‘ グループシェイプの場合は子シェイプも再帰的に掘り下げる
If shp.Type = visTypeGroup Then
Dim subShp As Visio.Shape
Dim i As Long
For i = 1 To shp.Shapes.Count
Set subShp = shp.Shapes(i)
Call InspectShapeRecursive(subShp, depth + 1)
Next i
End If
End Sub

‘ ——————————————————————————
‘ 指定シェイプの全セクションをループ処理する核心ロジック
‘ ——————————————————————————
Private Sub ScanAllSections(shp As Visio.Shape, indent As String)
‘ Visioが定義する主要なセクション定数の配列
‘ ※すべてのセクション定数を網羅的に配列化
Dim sectionIndices As Variant
sectionIndices = Array( _
visSectionObject, visSectionTransform, visSectionShapeLocPin, _
visSectionShapeTextXForm, visSectionFirst, visSectionParagraph, _
visSectionTabs, visSectionCharacter, visSectionScratch, _
visSectionConnectionPts, visSectionTextField, visSectionControls, _
visSectionAction, visSectionLayer, visSectionProp, _
visSectionForeign, visSectionEvents, visSectionLayerMember, _
visSectionHyperlink, visSectionSmartTag, visSectionAction _
)

Dim sIndex As Variant
Dim secObj As Visio.Section
Dim rowObj As Visio.Row
Dim cellObj As Visio.Cell

Dim s As Long, r As Long, c As Long

For Each sIndex In sectionIndices
On Error Resume Next
‘ セクションが存在するか確認するためオブジェクトを取得
Set secObj = Nothing
Set secObj = shp.Section(CLng(sIndex))
On Error GoTo 0

If Not secObj Is Nothing Then
If secObj.Count > 0 Then
For r = 0 To secObj.Count – 1
On Error Resume Next
Set rowObj = secObj.Item(r)
If Not rowObj Is Nothing Then
For c = 0 to rowObj.Count – 1
Set cellObj = Nothing
Set cellObj = rowObj.Cell(c)
If Not cellObj Is Nothing Then
‘ 数式(FormulaU)または値に意味があるものを抽出
Dim formU As String
formU = cellObj.FormulaU

‘ 空白やデフォルト値以外の「意味のあるカスタム設定」をログ出力
Debug.Print indent & “[Sec:” & sIndex & ” | Row:” & r & ” | Col:” & c & “] ” & _
cellObj.Name & ” = ” & formU & ” (Val: ” & cellObj.ResultIU & “)”
End If
Next c
End If
On Error GoTo 0
Next r
End If
End If
Next sIndex
End Sub

‘ ——————————————————————————
‘ シェイプタイプを文字列に変換するヘルパー
‘ ——————————————————————————
Private Function GetShapeTypeName(shapeType As Integer) As String
Select Case shapeType
Case visTypeGroup: GetShapeTypeName = “Group”
Case visTypeForeign: GetShapeTypeName = “Foreign”
Case visTypeShape: GetShapeTypeName = “Shape”
Case Else: GetShapeTypeName = “Unknown(” & shapeType & “)”
End Select
End Function

—

4. プロフェッショナルとしての実装解説・注意点

このコードを現場で運用するにあたり、チーフアーキテクトとしていくつかの重要な設計思想を補足しておく。

① `On Error Resume Next` の戦略的使用

VBAにおけるエラーハンドリングの基本は「局所化と復旧」だ。レガシー図面では、特定の行が破損していたり、保護(Protection)されていてアクセス権がないセルに遭遇することが多々ある。
このコードでは、セクションやセルの取得に失敗した場合に全体がクラッシュするのを防ぐため、あえて `On Error Resume Next` を挟み、不正なオブジェクトを華麗にスルーする設計にしている。

② `FormulaU` と `ResultIU` の使い分け

  • `FormulaU` (Universal Formula): セルに書き込まれている「生データの数式」を取得する。これがレガシー解析において最も重要。他のセルへの依存関係(例: `=TheText+Width`)が丸裸になる。
  • `ResultIU` (Internal Units): 内部単位(インチやラジアンなど)での評価値。数式がどのように解決されているかのスナップショットを確認できる。

③ パフォーマンスチューニング:画面描画の抑制

もし数百・数千のシェイプを持つ図面に対してこのマクロを走査する場合、`Application.ScreenUpdating = False` や `Application.ShowChanges = False` を前後に挟むのが鉄則だ。Visioはオブジェクトにアクセスするたびに画面描画を同期しようとする性質があるため、これを切るだけで実行速度が文字通り10倍以上変わる。

—

5. 発展:CSV出力やデータベース連携への布石

イミディエイトウィンドウに出力するだけでは、真の「保守業務の効率化」とは言えない。大規模なレガシー図面群(数十ファイル)を一括スキャンし、すべてのカスタムプロパティの棚卸しを行いたい場合は、このコードをベースにしてADODBやFileSystemObject (FSO)を組み合わせ、出力先をCSVやSQLiteなどの軽量データベースに変更すればよい。

「どの図面の、どのシェイプに、どんな呪われた数式が入っているか」を完全にインデックス化できれば、リプレイスプロジェクトの見積もり精度は劇的に跳ね上がる。

感覚や目視によるデバッグの時代は終わった。
コードを武器に、レガシーVisioの黒歴史をすべて暴き出せ。

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