【テクニカル・上級編】Page.FindShapesを用いたグラフィック属性(透過率・線種)指定の図形抽出フィルタリング – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:`Page.FindShapes`による超高速グラフィック属性フィルタリングの極意

ビジュアルな図面データ構造を扱うとき、多くの開発者が陥る最初の罠が「全シェイプの総なめ(再帰的走査)」によるパフォーマンスの急降下だ。数千のシェイプを持つ図面に対し、`Page.Shapes`コレクションを愚直にループさせ、個別に `.LineStyle` や `.Transparency` を判定していくアプローチは、VBAランタイムとCOM境界の往復オーバーヘッドを増大させ、実用に耐えない遅延を引き起こす。

Visio VBAのアーキテクチャにおいて、真にスケーラブルな自動化を実現するためには、ネイティブの高速検索APIである `Page.FindShapes` を使いこなし、COMの境界を超えるコストを最小限に抑えなければならない。

今回は、図面全体から「特定のグラフィック属性(線種、透過率など)」を持つ図形群をAPIレベルで一撃抽出し、一括制御・集計を行うための実践的アプローチを解説する。

1. なぜ `FindShapes` なのか? アーキテクチャの深層

VisioのCOMオブジェクトモデルにおいて、VBAからページ内のシェイプにアクセスするたびに、COMプロキシとネイティブC++コアの間でマーシャリングが発生する。数千個のシェイプを持つ図面で `For Each shp In activePage.Shapes` を回すコードは、数千回のプロセス間通信を引き起こしているに等しい。

一方、`Page.FindShapes` メソッド(およびその基底にあるShapeQuery機構)は、C++コア層のメモリ空間でインデックス走査および属性フィルタリングを並列に近い効率で実行する。
ここで重要になるのが、検索条件を指定するセレクタ構文(Query Syntax)だ。文字列ベースのクエリをAPIに直接渡すことで、VBA側でループを回す必要が一切なくなり、条件に一致したシェイプのコレクションだけが一括してVBA側に返還される。

2. 実装コード:属性ベース・超高速フィルタリングエンジン

以下のコードは、図面全ページを対象に、特定のグラフィック属性(例:特定の線種・塗りの透過率)を持つシェイプを瞬時に抽出し、ログ出力とプロパティの一括書き換えを行う実用モジュールである。

レガシーなVBA環境であっても、メモリリークを完全に排除するため、取得したオブジェクトは即座に `Nothing` 代入による解放を行う設計を徹底している。

Option Explicit

‘ ==============================================================================
‘ 処理名: 属性指定シェイプ高速抽出・一括操作エンジン
‘ 概要 : Page.FindShapesを活用し、指定したグラフィック属性を持つ図形を
COMのオーバーヘッドを極小化して抽出し、一括制御を行う。
‘ ==============================================================================
Public Sub ExecuteAdvancedShapeFiltering()
Dim vsoPage As Visio.Page
Dim vsoFoundShapes As Visio.Selection
Dim i As Long
Dim targetCount As Long

targetCount = 0

‘ 処理速度の極限最適化:画面描画とイベントを完全に凍結
With Application
.ScreenUpdating = False
.EventsEnabled = False
.SetWaitCursor True
End With

On Error GoTo ErrorHandler

‘ アクティブドキュメントの全ページを走査
For Each vsoPage In ActiveDocument.Pages

‘ 【重要】FindShapesによるクエリベースの高速フィルタリング
‘ 例: 塗りつぶしの透過率が 50% 以上 (Transparency > 0.5) のシェイプを抽出
‘ ※Visioのクエリ構文はセル名と関数を組み合わせます
Dim queryCriteria As String
queryCriteria = “Prop.CustProp = AND FillTransparency >= 0.5”

‘ FindShapesメソッドの実行
‘ 引数: 検索フラグ(visFindShapesAncestors等), 検索文字列, 結果格納用セレクション
Set vsoFoundShapes = vsoPage.FindShapes(visFindShapesRecursive, queryCriteria)

If Not vsoFoundShapes Is Nothing Then
If vsoFoundShapes.Count > 0 Then
Debug.Print “Page [” & vsoPage.Name & “] 該当シェイプ数: ” & vsoFoundShapes.Count

For i = 1 To vsoFoundShapes.Count
Dim shp As Visio.Shape
Set shp = vsoFoundShapes(i)

‘ 抽出されたシェイプに対するビジネスロジック(例:枠線を赤色に変更)
Call ApplyHighPerformanceStyling(shp)

targetCount = targetCount + 1

‘ ループ内でのオブジェクト参照の明示的破棄
Set shp = Nothing
Next i
End If

‘ セレクション自体の解放
Set vsoFoundShapes = Nothing
End If

Next vsoPage

MsgBox “処理完了: 該当シェイプ総数 ” & targetCount & ” 件の属性を更新しました。”, vbInformation, “API最適化エンジン”

CleanUp:
‘ 環境の復元(例外時も必ず実行)
With Application
.ScreenUpdating = True
.EventsEnabled = True
.SetWaitCursor False
End With
Exit Sub

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

‘ ==============================================================================
‘ 補助プロシージャ: 個別シェイプへの属性適用とメモリ管理
‘ ==============================================================================
Private Sub ApplyHighPerformanceStyling(ByRef targetShape As Visio.Shape)
On Error GoTo ShapeError

‘ セルへの直接アクセスによる高速書式変更
‘ CellsSRC 引数: セクション, 行, 列
targetShape.CellsSRC(visSectionObject, visRowLine, visLineColor).FormulaU = “RGB(255, 0, 0)”
targetShape.CellsSRC(visSectionObject, visRowLine, visLinePattern).ResultIU = 2 ‘ 破線などに変更

Exit Sub
ShapeError:
‘ 読み取り専用保護シェイプやグループマスター等の例外をキャッチして継続
Debug.Print “Warning: シェイプID ” & targetShape.ID & ” の書式変更に失敗しました。”
End Sub

3. チーフアーキテクトが教える:実務で活きる極限の知見

A. クエリ構文のチューニングとパフォーマンスの罠

`FindShapes` の引数に渡すクエリ文字列は、VisioのSheetSheet(ShapeSheet)のセル名(例: `FillTransparency`, `LinePattern`)に完全準拠している。
ここで文字列の構築を誤ると、エラーを返さずに「0件ヒット」となるか、逆にインデックスが効かずにフルスキャンに落ち込む。正確なセル名を特定するために、あらかじめ開発タブの「シェイプシートの表示」から対象セルのローカル名を確認しておくこと。

B. COMオブジェクトのライフサイクル管理とメモリリーク対策

VBAは自動ガベージコレクションを備えているが、VisioのCOMオブジェクト(特に `Selection` や `Shape` コレクション)は参照カウントが複雑に絡み合う。
ループ内で `Set shp = vsoFoundShapes(i)` と宣言したポインタは、ループの終端で `Set shp = Nothing` と明示的にデクリメントしないと、VBAの背後にあるCOM RCW(Runtime Callable Wrapper)のメモリが肥大化し、大規模図面処理時に「メモリ不足 (Out of Memory)」でクラッシュする原因になる。

C. 画面描画の凍結(`ScreenUpdating`)の絶対性

大量のシェイプを一括操作する場合、描画エンジンの更新を有効にしたままでは、1つのプロパティを変更するたびにVisio画面全体の再描画(Redraw)が走り、処理速度が数百倍低下する。
必ず `Application.ScreenUpdating = False` と `Application.EventsEnabled = False` でランタイムをサイレントモードにし、異常終了時(`On Error GoTo`)も含めて確実に復元するイディオムを組み込むこと。

総括

VBAは「おもちゃの言語」ではない。オブジェクトモデルの背後にあるC++コアの挙動と、COMのメモリ管理の仕組みを熟知したエンジニアが書いたコードは、専用のデスクトップアプリケーションと同等のパフォーマンスを発揮する。
今回解説した `FindShapes` によるフィルタリング戦略は、巨大なプラント図面、ネットワーク図、あるいは自動生成されたシステム構成図のバッチ処理において、あなたのシステムを圧倒的な高みへと導くはずだ。

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