Visio VBAを掌握する極限の知見:ShapeSheet全セクション動的走査による未知のプロパティ構造解析
Visioの真の姿は、単なる「お絵描きツール」ではない。それは各図形(Shape)の内部に多次元の数式データベースを持つ、極めて堅牢なベクトル演算エンジンである。
サードパーティ製のステンシル、あるいは他システムからインポートされた複雑なグループ図形。これらを扱うとき、我々エンジニアは常に「ブラックボックスの恐怖」に直面する。UI上からは見えない隠しセクション、予測不能なカスタムプロパティ、あるいは意図せぬ数式の連鎖。これらを暴くためには、ドキュメントの表面をなぞるだけでは不十分だ。
今回は、Visio VBAのオブジェクトモデルの深淵に潜り込み、`Shape.Section` プロパティを起点として、図形に紐づくすべてのセクション、すべての行、すべてのセルを全自動で網羅・解析する「構造解析エンジン」の構築手法を伝授する。
レガシーなCOMコンポーネントの挙動を熟知し、メモリの限界まで酷使してきたアーキテクトだけが知る、極限の知見をここに開示する。
—
1. Visioオブジェクトモデルの残酷な真実とメモリ管理
Visio VBAにおける最大の罠は、オブジェクトの暗黙的な生成と参照リークにある。
`ActivePage.Shapes(1).CellsSRC(…)` のようなドット繋ぎのコードは、一見してスマートに見えるが、裏側で無数のCOMラッパーオブジェクト(IUnknown / IDispatch)をHeap上に生成し、VBAのランタイムが自動解放するタイミングを逸してメモリリークを引き起こす。
特に数千個のセルを持つ複雑な図形を再帰的に走査する場合、以下の鉄則を遵守しなければ、Visioは容赦なくメモリ不足(Error 7)でクラッシュする。
1. オブジェクト変数の明示的スコープ管理と即時解放
`Set obj = Nothing` をループの都度実行し、COM参照カウンタを確実にデクリメントする。
2. Variant型の乱用禁止
セクションや行のインデックス走査において、不必要なVariant型は型変換のオーバーヘッドを生む。明示的な `Integer` や `Long` を使用せよ。
3. 画面描画の完全な凍結
`Application.ScreenUpdating = False` だけでは不十分だ。イベントの無効化と組み合わせ、Visioの再描画エンジンを完全に沈黙させる。
—
2. ShapeSheet階層構造の数理モデル
VisioのShapeSheetは、以下の3階層ツリー構造で成り立っている。
Shape (図形)
┗ Section (セクション: 例. visSectionProp, visSectionCharacter 等)
┗ Row (行: 例. Prop.Row_1, Actions.Action_1 等)
┗ Cell (セル: 例. Value, Label, Prompt 等)
これをプログラムから完全網羅するためには、定数として定義されたセクションID(`visSectionFirst` から `visSectionLast` まで、あるいはカスタムセクション)を総当たりでチェックし、さらに各セクション内の行インデックス、セルインデックスへと潜り込む必要がある。
—
3. 実装:ShapeSheet全セクション動的走査エンジン
以下のコードは、選択された図形(またはページ内の全図形)のShapeSheetを徹底的にハッキングし、イミディエイトウィンドウ(あるいはログファイル)へ全セクション・全プロパティの数式と現在値をダンプする実用的なVBAプロシージャである。
‘ ==============================================================================
‘ módulo: ModShapeInspector
‘ 概要: 選択されたVisioシェイプのShapeSheet構造を限界まで解析するチートエンジン
‘ ==============================================================================
Option Explicit
Public Sub InspectSelectedShapeDeeply()
Dim vsoShape As Visio.Shape
Dim startTime As Double
startTime = Timer
‘ 1. 動作環境の最適化(極限のパフォーマンス追求)
With Visio.Application
.ScreenUpdating = False
.EventsEnabled = False
.UndoEnabled = False
End With
On Error GoTo ErrorHandler
If ActiveWindow.Selection.Count = 0 Then
MsgBox “解析対象のシェイプを選択してください。”, vbExclamation, “構造解析エンジン”
GoTo CleanUp
End If
Set vsoShape = ActiveWindow.Selection(1)
Debug.Print “========================================================”
Debug.Print ” [INFO] ShapeSheet Deep Inspector Started”
Debug.Print ” Target Shape ID: ” & vsoShape.ID & ” | Name: ” & vsoShape.Name
Debug.Print “========================================================”
‘ 再帰的にシェイプ(グループ含む)を走査
Call TraverseShapeRecursive(vsoShape, 0)
Debug.Print “========================================================”
Debug.Print ” [INFO] Inspection Completed. Elapsed Time: ” & Format(Timer – startTime, “0.00”) & ” sec”
Debug.Print “========================================================”
CleanUp:
‘ 2. 環境の復元
With Visio.Application
.ScreenUpdating = True
.EventsEnabled = True
.UndoEnabled = True
End With
Exit Sub
ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “API Error”
Resume CleanUp
End Sub
‘ ——————————————————————————
‘ 内部プロシージャ: シェイプの階層(グループ考慮)を再帰走査
‘ ——————————————————————————
Private Sub TraverseShapeRecursive(ByVal shp As Visio.Shape, ByVal depth As Integer)
Dim indent As String
indent = String(depth 2, ” “)
Debug.Print indent & “+- [Shape] ID=” & shp.ID & “, Name=” & shp.Name & “, Type=” & shp.Type
‘ セクションの走査
Call ScanSections(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 TraverseShapeRecursive(subShp, depth + 1)
Set subShp = Nothing ‘ メモリ解放
Next i
End If
End Sub
‘ ——————————————————————————
‘ 内部プロシージャ: 指定シェイプの全セクション・行・セルを総当たり解析
‘ ——————————————————————————
Private Sub ScanSections(ByVal shp As Visio.Shape, ByVal indent As String)
Dim secIdx As Integer
Dim rowIdx As Integer
Dim cellIdx As Integer
Dim secCount As Integer
Dim rowCount As Integer
Dim cellCount As Integer
Dim vsoSection As Visio.Section
Dim vsoRow As Visio.Row
Dim vsoCell As Visio.Cell
On Error Resume Next
secCount = shp.SectionCount
For secIdx = 0 To secCount – 1
‘ SectionIndexからオブジェクトを取得(存在しない場合はエラー回避)
Set vsoSection = shp.Sections(secIdx)
If Err.Number = 0 And Not vsoSection Is Nothing Then
Debug.Print indent & “-> [Section] Index=” & secIdx & ” (Exist)”
rowCount = vsoSection.Count
For rowIdx = 0 To rowCount – 1
Set vsoRow = vsoSection.Rows(rowIdx)
If Err.Number = 0 And Not vsoRow Is Nothing Then
cellCount = vsoRow.Count
For cellIdx = 0 To cellCount – 1
Set vsoCell = vsoRow.Cells(cellIdx)
If Err.Number = 0 And Not vsoCell Is Nothing Then
‘ 数式(FormulaU)または値に意味があるものだけ、あるいは全てを出力
Dim fml As String, val As String, cName As String
fml = vsoCell.FormulaU
val = vsoCell.ResultStr(visNoCast)
cName = vsoCell.Name
‘ デバッグ出力(情報量が多いため、必要に応じてフィルタリング可能)
Debug.Print indent & ” R” & rowIdx & “/C” & cellIdx & ” [” & cName & “] ” & _
“Formula: ” & fml & ” | Value: ” & val
Set vsoCell = Nothing
End If
Err.Clear
Next cellIdx
Set vsoRow = Nothing
End If
Err.Clear
Next rowIdx
Set vsoSection = Nothing
End If
Err.Clear
Next secIdx
On Error GoTo 0
End Sub
—
4. チーフアーキテクトの視点:この技術がもたらす実務的優位性
このコードを実行すると、サードパーティ製図形がいかに巧妙にセクション(特に `visSectionProp` や `visSectionAction`、さらには未知の `visSectionUser`)を利用しているかが手に取るようにわかる。
1. レガシー移行時の仕様逆解析
ドキュメントが失われた社内ニッチツールにおいて、図形がどのような計算ロジック(位置連動、色変化、カスタムプロパティのバインド)を持っているか、UIのプロパティウィンドウを開かずに一瞬で全抽出できる。
2. 外部システム連携(C# / .NET / Webとの統合)
Visio図形をSVGやHTML5キャンバスに変換、あるいは独自フォーマットにシリアライズする際、この動的走査エンジンをベースにしたパーサーを組むことで、「どの図形がどのような数式依存関係を持っているか」の依存グラフ(Dependency Graph)を完全にプログラム側で再構築可能になる。
—
5. 結びにかえて
VBAはレガシーな言語と揶揄されることがある。しかし、Visioという巨大なCOMプラットフォームの深部にダイレクトにアクセスし、そのメモリ構造を意のままに操るための「最も鋭利な刃」であることに変わりはない。
オブジェクトのライフサイクルを支配し、エラーハンドリングの網を抜け、あらゆるブラックボックスをコードの力で丸裸にする。これこそが、真のエンジニアリングである。
あなたの手元にあるその複雑な図形も、このスニペットを通せば、ただの「開かれたデータ構造」に過ぎない。さあ、IDEを開き、コンソールに無限のログを溺れさせたまえ。
