【テクニカル・上級編】Shape.Typeプロパティによる図形判定:グループ・コネクタ・ガイド線をVBAで安全にフィルタリング – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:Shape.Typeによる図形の厳格なフィルタリングとメモリ防衛術

Visio VBAにおける一括処理のコードを書くとき、あなたはページ内の全シェイプを単純な `For Each` ループで回し、その直後に予期せぬ `Run-time error` で頭を抱えたことはないだろうか。

「グループ化された子シェイプを処理しようとしてレイアウトが崩壊した」
「コネクタの端点を操作しようとして無効なプロパティアクセスエラーが発生した」
「ガイド線(導線)まで意図せず移動・変形してしまい、図面がゴミ屋敷と化した」

これらは、Visioのオブジェクトモデルの階層構造と、`Shape.Type` プロパティの本質を見誤っているがために起こる、アマチュア特有の事故だ。

本稿では、数百万ステップ規模の図面自動生成システムや、CADデータからの変換パイプラインを構築してきたアーキテクトの視点から、`Shape.Type` を用いた安全かつ高速な図形フィルタリングの極意を解説する。

1. Visioオブジェクトモデルの暗部:なぜ全シェイプ走査は地雷原なのか?

Visioの `Page.Shapes` コレクションは、単なる平坦なリストではない。そこには「マスターシェイプのインスタンス」「グループの親」「グループ内の子(Sub-shape)」「コネクタ」「ガイド線」が混在している。

特に、`ActivePage.Shapes` をそのままループさせると、グループ化された親シェイプとその中にある子シェイプが二重に列挙されるというVisio特有の仕様(あるいは罠)に直面する。このレイヤー構造を無視して直接プロパティを操作すると、ドキュメントの整合性が破壊される。

ここで、Visioが提供する `VisShapeTypes` 列挙体(`Shape.Type` が返す値)の正体を正確に把握する必要がある。

  • `visTypeShape` (1): 通常の2次元シェイプ(マスタからドロップされたもの等)
  • `visTypeGroup` (2): グループシェイプ(中に子を内包するコンテナ)
  • `visTypeForeign` (3): 外部オブジェクト(OLEオブジェクトやCAD図面など)
  • `visTypeGuide` (4): ガイド線・ガイドポイント(非印刷のレイアウト用)

※コネクタ(Connector)は、`Shape.Type` が `visTypeShape` であても、Glue(接着)情報やコネクタ特有のセル(BeginX, EndX等)を持っているかどうかで厳密に判別する必要がある点に注意したい。

2. 【実装パターン】安全防衛型シェイプ走査エンジン

実務で使える、堅牢性を極限まで高めたVBAコードのテンプレートを提示する。
このコードでは、予期せぬエラーを防ぐための型判定に加え、VBA特有のCOMオブジェクト解放(メモリ最適化)のベストプラクティスを網羅している。

Option Explicit

Public Sub ProcessPageShapesSafely()
‘ —————————————————————–
‘ チーフアーキテクト直伝:安全防衛型シェイプ走査テンプレート
‘ —————————————————————–
Dim vsoPage As Visio.Page
Set vsoPage = ActivePage

Dim vsoShapes As Visio.Shapes
Set vsoShapes = vsoPage.Shapes

Dim totalCount As Long
totalCount = vsoShapes.Count

Dim i As Long
Dim vsoShape As Visio.Shape

‘ パフォーマンス最適化:画面描画とイベントを一時停止
Application.ScreenUpdating = False
Application.EventEnabled = False

On Error GoTo ErrorHandler

‘ 逆順ループの採用:
‘ コレクションから要素を削除・動的変更する場合や、
‘ 予期せぬインデックスシフトを防ぐためにインデックスによる逆順走査が鉄則。
For i = totalCount To 1 Step -1
Set vsoShape = vsoShapes(i)

‘ 1. ガイド線の除外(レイアウト用オブジェクトは処理対象外とする)
If vsoShape.Type = VisShapeTypes.visTypeGuide Then
GoTo ContinueLoop
End If

‘ 2. グループシェイプの判定とハンドリング
If vsoShape.Type = VisShapeTypes.visTypeGroup Then
‘ グループ親自体の処理、または再帰的に子シェイプを処理する関数へジャンプ
Call ProcessGroupShape(vsoShape)
GoTo ContinueLoop
End If

‘ 3. コネクタの判定
‘ Shape.Typeだけではコネクタ判定が不十分な場合があるため、マスター名やコネクタ特有のセルの有無を検証
If IsConnector(vsoShape) Then
‘ コネクタ固有の処理(経路の最適化など)
Call ProcessConnector(vsoShape)
GoTo ContinueLoop
End If

‘ 4. 通常シェイプ(2Dシェイプなど)に対するメイン処理
Call ProcessStandardShape(vsoShape)

ContinueLoop:
‘ COMオブジェクトの参照を確実に解放(メモリリーク防止)
Set vsoShape = Nothing
Next i

CleanUp:
‘ 描画とイベントの復元
Application.ScreenUpdating = True
Application.EventEnabled = True
Set vsoShapes = Nothing
Set vsoPage = Nothing
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “System Error”
Resume CleanUp
End Sub

‘ —————————————————————–
‘ サブプロシージャ:コネクタの厳密な判定
‘ —————————————————————–
Private Function IsConnector(vsoShape As Visio.Shape) As Boolean
On Error Resume Next
‘ Connectsプロパティや、コネクタ特有のセルの存在で判定するのが最も確実
Dim formula As String
formula = vsoShape.CellsSRC(visSectionObject, visRowShapeLayout, visSLORouteStyle).Formula
If Err.Number = 0 Then
IsConnector = True
Else
IsConnector = False
End If
On Error GoTo 0
End Function

Private Sub ProcessGroupShape(vsoGroup As Visio.Shape)
‘ グループ親の処理
Debug.Print “Group Found: ” & vsoGroup.Name

‘ 必要に応じて、グループ内のサブシェイプを再帰的に走査
Dim subShapes As Visio.Shapes
Set subShapes = vsoGroup.Shapes
Dim j As Long
For j = 1 To subShapes.Count
‘ ここでも同様にTypeによるフィルタリングを適用可能
Next j
Set subShapes = Nothing
End Sub

Private Sub ProcessConnector(vsoConn As Visio.Shape)
Debug.Print “Connector Found: ” & vsoConn.Name
End Sub

Private Sub ProcessStandardShape(vsoShape As Visio.Shape)
Debug.Print “Standard Shape Found: ” & vsoShape.Name
End Sub

3. レガシー環境とメモリ管理の極意:なぜ `Set 〇〇 = Nothing` が必須なのか?

VBAをただの「お絵描きスクリプト言語」だと思っていないだろうか。Visio VBAの裏側では、COM(Component Object Model)の巨大なプロセス間通信が走っている。

特に数千個のシェイプを持つ巨大な図面(プラント図や大規模ネットワーク図など)を一括処理する際、ループ内でオブジェクト変数を使い捨てにし、かつ `Set vsoShape = Nothing` による参照解放を行わないと、VBAランタイムの背後でCOM参照カウントが肥大化し、メモリリーク(最悪の場合はVisio自体のクラッシュ)を引き起こす。

さらに、パフォーマンスの観点から以下の2行の呪文を忘れてはならない。

Application.ScreenUpdating = False
Application.EventEnabled = False

これが漏れていると、シェイプのプロパティを1つ変更するたびにVisioのGUI描画エンジンが再描画走り、処理時間が10倍から100倍に跳ね上がる。プロフェッショナルたるもの、リソースの消費には常に冷徹であれ。

4. チーフアーキテクトからの提言

実務でVisio VBAを扱うエンジニアに伝えたいのは、「動けばいい」というコードの寿命は、図面の複雑化と比例して数日単位で尽きるということだ。

今回解説した `Shape.Type` による厳格なフィルタリングと、エラーハンドリング、そして確実なメモリのライフサイクル管理は、大規模なシステム連携や、外部データベースからVisioを自動生成するミドルウェア開発において、エンジニアの生命線を守る防壁となる。

甘いコードを書くな。すべてのオブジェクトを把握し、制御下に置け。それがVisioを真に掌握する唯一の道である。

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