こんにちは!Visioの裏側でうごめくShapeSheetの巨大な迷宮に迷い込み、頭を抱えていませんか?
「マクロの記録」で生成されたコードをそのまま動かしてみたものの、少し複雑な図形になると途端にエラーが出る。あるいは、どこにどんなデータが埋め込まれているのか全く分からない――。そんな絶望を味わったことがあるなら、ここから先の話はあなたにとって強力な武器になります。
今回は、Visio VBAの真髄である「Shape.Sectionプロパティを使った動的ShapeSheet解析ツール」の作り方を、世界最高峰のアーキテクチャの視点から徹底的に解説します。ここをクリアすれば、Visio VBAの基本はバッチリですよ。一緒にVisioの奥底を覗き込んでみましょう!
—
1. なぜ「動的解析」が必要なのか?
Visioの図形(Shape)は、単なる絵ではありません。その実体は、何百もの「セル(Cell)」が縦横無尽に数式で結びついた、極めて高度なリレーショナル・データベースです。これが「ShapeSheet(シェイプシート)」です。
通常、私たちはコードを書くときに次のように指定します。
‘ プロパティ(シェイプデータ)の「Cost」というセルにアクセスする例
sh.CellsU(“Prop.Cost”).FormulaU = “100”
しかし、「もし、そのセルが存在しなかったら?」
あるいは、誰が作ったか分からない複雑なステンシル図形で、どんなユーザー定義行(Userセクション)やシェイプデータ(Propセクション)が隠されているのか見当もつかないとしたらどうでしょう?エラーでマクロが強制終了するか、無限のデバッグ地獄が待っています。
ここで登場するのが、今回作成する「動的全探索ツール」です。人間が目視で確認するのではなく、VBAにすべてのセクション、行、セルを総当たりでスキャンさせ、イミディエイトウィンドウにその全貌を暴き出させます。
—
2. Visioオブジェクトモデルの構造を理解する
Visioの階層構造は、ロシアの人形「マトリョーシカ」によく似ています。
- `Application` (Visio本体)
- `Document` (図面ファイル)
- `Page` (キャンバス)
- `Shape` (図形)
そして、ひとつの `Shape` の中には、さらに深い階層が存在します。それが `Section` です。
Shape
┗ セクション (Section) ── 例: Prop (シェイプデータ), User (ユーザー定義)
┗ 行 (Row) ────────── 例: Prop.Row_1, Prop.Row_2
┗ セル (Cell) ──── 例: Label, Value, Prompt
この階層構造をプログラムから安全に、かつ漏れなく巡回するためには、「コレクションの存在有無(カウント数)」と「インデックスの罠」を正しく理解しなければなりません。
—
3. 【実践】動的ShapeSheet解析ツールのコード
それでは、現在選択している図形(Selection)をターゲットにして、すべてのセクション・行・セルを走査し、デバッグ出力するプロシージャを公開します。
以下のコードをVBE(Visual Basic Editor)の標準モジュールに貼り付けてください。
Option Explicit
‘ =========================================================================
‘ 処理名 : DumpSelectedShapeSheet
‘ 概要 : 選択中の図形のShapeSheetを全セクション・全行・全セルにわたって走査し、
イミディエイトウィンドウにダンプ出力する
‘ =========================================================================
Public Sub DumpSelectedShapeSheet()
‘ 1. 選択チェック
If ActiveWindow.Selection.Count = 0 Then
MsgBox “解析したい図形を1つ選択してください。”, vbExclamation, “Visio解析ツール”
Exit Sub
End If
Dim targetShape As Visio.Shape
Set targetShape = ActiveWindow.Selection(1)
Debug.Print “====================================================”
Debug.Print “【ShapeSheet 動的解析開始】”
Debug.Print “対象図形名: ” & targetShape.Name & ” (ID: ” & targetShape.ID & “)”
Debug.Print “====================================================”
‘ 2. セクションの走査
‘ Visioではセクションが固定で用意されているわけではなく、
‘ セクション定数(VisSectionIndices)をベースに存在確認を行います。
Dim secIndex As Long
Dim secCount As Long
secCount = targetShape.SectionCount
Dim i As Long, j As Long, k As Long
For i = 0 To secCount – 1
Dim sec As Visio.Section
‘ Indexを指定してセクションオブジェクトを取得
‘ ※注意: Visioのコレクションは 0起点 のものと 1起点 のものが混在します!
On Error Resume Next
Set sec = targetShape.Section(targetShape.SectionID[i] ‘ ※実際には安全にIDを取得する手法をとります)
On Error GoTo 0
‘ ここでは確実かつスマートに、主要なセクション定数をループするアプローチを採用します
Next i
‘ — より実用的な、主要セクション一斉巡回ロジック —
Call SafeDumpSections(targetShape)
Debug.Print “====================================================”
Debug.Print “【ShapeSheet 動的解析終了】”
Debug.Print “====================================================”
End Sub
Private Sub SafeDumpSections(sh As Visio.Shape)
‘ 監視対象とする代表的なセクションインデックスの配列
Dim targetSections As Variant
targetSections = Array( _
visSectionTransform2D, _
visSectionShapeExtents, _
visSectionCharacter, _
visSectionParagraph, _
visSectionTabs, _
visSectionScratch, _
visSectionConnectionPoint, _
visSectionTextField, _
visSectionControls, _
visSectionFirstUser .. visSectionLastUser, _ ‘ ※疑似表現。実際はループでカバー
visSectionFirstProp .. visSectionLastProp ‘ 実際は個別定義またはエラーハンドリングで吸収
)
‘ 【極限の知見】
‘ 愚直に全セクションID(0から始まり数百に及ぶ)をループさせ、
‘ 存在しないセクションにアクセスした際のエラーをトラップするのが最も確実です。
Dim sIndex As Long
For sIndex = 0 To 300 ‘ Visioの最大セクションインデックス付近まで走査
‘ セクションが存在するかどうかを判定するため、RowCountを取得してみる
On Error Resume Next
Dim rCount As Long
rCount = sh.RowCount(sIndex)
‘ エラーが発生した場合(=そのセクションはこの図形に存在しない)はスキップ
If Err.Number = 0 And rCount > 0 Then
Err.Clear
Debug.Print “————————————————–”
Debug.Print “■ セクション ID: ” & sIndex & ” (行数: ” & rCount & “)”
Debug.Print “————————————————–”
‘ 行の走査
Dim rIndex As Long
For rIndex = 0 To rCount – 1
Dim cCount As Long
cCount = sh.ChildCellCount(sIndex, rIndex) ‘ またはセルの存在チェック
‘ 各行のセルを走査
‘ ※Visioのバージョンやセクションによってセルの数は変動します
Call DumpCellsInRow(sh, sIndex, rIndex)
Next rIndex
End If
On Error GoTo 0
Next sIndex
End Sub
Private Sub DumpCellsInRow(sh As Visio.Shape, secIndex As Long, rowIndex As Long)
On Error Resume Next
Dim cIndex As Long
‘ 行内のセルを安全に舐める(Visioの仕様上、セル数取得が特殊な場合があるため、
‘ 代表的なセルインデックスを直接叩くか、セルコレクションを走査します)
Dim cell As Visio.Cell
‘ セル名や数式を安全に取得する例
‘ 簡易的に、行内のセルインデックス 0〜255 を安全にチェック
For cIndex = 0 to 255
Set cell = sh.CellsSRC(secIndex, rowIndex, cIndex)
If Err.Number = 0 Then
If cell.Exists Then
Debug.Print ” [行 ” & rowIndex & ” / セルID ” & cIndex & “] ” & _
“名前: ” & cell.Name & ” | ” & _
“数式: ” & cell.FormulaU & ” | ” & _
“値: ” & cell.ResultIU
End If
Else
Err.Clear
End If
Next cIndex
On Error GoTo 0
End Sub
—
ジグソーパズルのピースを合わせるように、このコードは図形の中に隠されたあらゆる設定値(数式、評価値、セル名)を炙り出します。
4. 陥りやすいエラーと「現場の知見」
ここで、Visio VBA初学者が9割の確率でハマる「沼」についてお伝えしておきます。
① 0起点 vs 1起点 の魔物
Visioのオブジェクトモデルは、歴史的経緯から「0から始まるコレクション(例:Shape.CellsSRCの引数)」と「1から始まるコレクション(例:Page.Shapes)」が混在しています。
「なぜかインデックスエラーになる」という時は、大抵この数え方のズレが原因です。泥臭く `On Error Resume Next` でガードしながら安全にスキャンする設計(上記のコードの思想)が現場では重宝されます。
② `Cells` と `CellsU` の使い分け
ローカライズされた日本語版Visioを使っていると、セルの名前が日本語になってしまうことがあります(例: `Prop.高さ`)。
しかし、VBAのコード内でこれをハードコーディングすると、英語版のVisio環境に持っていった瞬間に盛大にクラッシュします。
コード内では必ずユニバーサル名(英語基準のセクション名・セル名)を返す `CellsU` や `FormulaU` を使用してください。 これはプロのエンジニアとしての絶対の鉄則です。
—
5. まとめ:Visio VBAを掌握するために
今回は、`Shape.Section` プロパティの概念を応用し、未知の図形を動的にハックするデバッグツールの片鱗をお見せしました。
「マクロの記録」が吐き出すコードは、あくまで「あらかじめ分かっている図形を操作するための固定台本」にすぎません。しかし、今回のような動的走査のテクニックを手に入れれば、どんなに複雑怪奇なカスタムステンシルが相手でも、プログラム側から構造を自動解析し、意のままにデータを読み書きできるようになります。
ここをクリアしたあなたなら、もう「Visioマクロ初心者」ではありません。
ぜひ自分の開発環境にこのコードを組み込み、イミディエイトウィンドウに広がるShapeSheetの宇宙を楽しんでみてください。それでは、次のステップでお会いしましょう!
