Visio VBAを掌握する極限の知見:Selectionオブジェクトと `CreateSelection` の全貌
Visioにおけるマクロ開発の現場において、「画面上の図形をいかに効率的に制御するか」は、パフォーマンスとユーザーエクスペリエンス(UX)を左右するクリティカルな要件である。
多くの初学者が陥る罠は、Shapeオブジェクトを一つずつループさせ、`Shape.Select` メソッドを泥臭く呼び出すというアプローチだ。数千の図形を含む大規模なダイアグラムでこれを実行すれば、画面の再描画(Redraw)が走るたびにCPUは飽和し、UIはフリーズする。
シニアエンジニアが知るべき真実は一つ。「Visioの選択状態は、メモリ上で一括構築し、トランザクションとしてUIに流し込むべきである」。
今回は、`Shape.CreateSelection` メソッドと `Selection` オブジェクトを極限まで使いこなし、エンタープライズレベルの高速処理と堅牢性を実現するアーキテクチャを解説する。
—
1. Visioオブジェクトモデルの裏側:なぜ `Select` の乱用は悪なのか
VisioのUIスレッドとVBAの実行スレッドは密接に結合している。コードから `.Select` を呼ぶたびに、以下の重厚な処理が内部で発生する。
1. ウィンドウの選択リスト(Selection List)の書き換え
2. 図形のハンドル(選択枠)の再計算
3. ウィンドウの無効化(Invalidate)と再描画イベントの発生
これを動的なループ内で繰り返すと、O(N) オーダーの描画負荷がシステムを直撃する。
これに対し、`Page.CreateSelection` を用いたアプローチは、メモリ上で仮想的なグループを構築し、最後に一撃でウィンドウへバインドする。これにより、UIの再描画コストを実質的にゼロ(または1回)に抑え込むことが可能になる。
—
2. 実装:`CreateSelection` による高速・高度選択制御
以下のコードは、特定の条件(例:特定のレイヤーに属する、あるいは特定のマスタシェイプから派生した図形)をメモリ上で網羅的に収集し、瞬時に選択状態へ昇華させる実用的なモジュールだ。
レガシー環境や大規模図形を扱う現場を想定し、画面描画の抑制(`ScreenUpdating`)とエラーハンドリングを完備している。
Option Explicit
”’
”’
Public Sub SelectShapesByCondition()
‘ 実行時のパフォーマンス低下を防ぐための最適化定数
Const VIS_SEL_INIT = 2 ‘ 新規選択
Const VIS_SEL_SRC_SEL = 3 ‘ ソースから選択
Dim vsoPage As Visio.Page
Dim vsoShape As Visio.Shape
Dim vsoTargetShapes() As Visio.Shape
Dim targetCount As Long
‘ メモリ最適化のための変数
Dim vsoSelection As Visio.Selection
Dim i As Long
‘ 1. コンテキストの取得
Set vsoPage = ActivePage
If vsoPage Is Nothing Then
MsgBox “アクティブなページが存在しません。”, vbCritical, “致命的エラー”
Exit Sub
End If
‘ 2. 画面描画とイベントの完全停止(極限のパフォーマンス追求)
Application.ScreenUpdating = False
Application.EventsEnabled = False
On Error GoTo ErrorHandler
‘ 配列の初期化(動的確保)
targetCount = 0
ReDim vsoTargetShapes(0 To vsoPage.Shapes.Count – 1)
‘ 3. ロジックによる図形の高速フィルタリング(メモリ上での走査)
For Each vsoShape In vsoPage.Shapes
‘ 【条件判定の例】名前に “Process_” が含まれ、かつ2D図形であるもの
If InStr(1 vsoShape.Name, “Process_”, vbTextCompare) > 0 Then
If vsoShape.OneD <> 0 Then ‘ 0 = 2Dシェイプ
Set vsoTargetShapes(targetCount) = vsoShape
targetCount = targetCount + 1
End If
End If
Next vsoShape
‘ 対象が存在しない場合は早期リターン
If targetCount = 0 Then
MsgBox “条件に合致する図形は検出されませんでした。”, vbInformation, “情報”
GoTo CleanUp
End If
‘ 実数に合わせて配列をリサイズ
ReDim Preserve vsoTargetShapes(0 To targetCount – 1)
‘ 4. CreateSelection による Selection オブジェクトの生成
‘ ※最初の図形をベースにSelectionを作成し、残りを追加していく手法が最も確実
Set vsoSelection = vsoPage.CreateSelection(visSelTypeEmpty)
For i = LBound(vsoTargetShapes) To UBound(vsoTargetShapes)
vsoSelection.Select vsoTargetShapes(i), visSelect
Next i
‘ 5. ウィンドウへの一括適用(ここで初めてUIが更新される)
ActiveWindow.Selection = vsoSelection
CleanUp:
‘ 6. 描画の復元とオブジェクトの明示的解放(メモリリーク防止)
Application.ScreenUpdating = True
Application.EventsEnabled = True
‘ COMオブジェクトの参照破棄
Set vsoSelection = Nothing
Set vsoPage = Nothing
Erase vsoTargetShapes
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Sub
—
3. シニアエンジニアが押さえるべきアーキテクチャの急所
オブジェクトのライフサイクルとメモリ管理
VBAの裏側で動いているVisioのCOMオブジェクトは、参照カウント方式で管理されている。特に `Selection` オブジェクトや多数の `Shape` 配列を扱うコードでは、処理終了後に `Set variable = Nothing` を明示的に行わないと、VBAのガベージコレクタが即座にメモリを解放せず、Visioプロセスが肥大化する(メモリリーク)。
特にマクロを繰り返し実行する定常業務ツールにおいて、このリークは致命的なExcel/Visioの強制終了を引き起こす。上記のコードでは、`CleanUp` ラベリングによる確実な参照破棄を担保している。
ScreenUpdating と EventsEnabled の二段構え
大規模図面での自動処理において、`Application.ScreenUpdating = False` だけでは不十分な場合がある。図形の選択状態が変わるたびに `SelectionChanged` などのイベントハンドラが走っていると、処理速度が桁違いに低下する。
必ず `Application.EventsEnabled = False` を併用し、VBA実行中のシステム割り込みを完全にシャットアウトすること。
Selection オブジェクトの応用:一括プロパティ操作
`CreateSelection` で得られた `Selection` オブジェクトは、単に「画面上で選択されているように見せる」だけではない。Selectionに対して直接プロパティ(例: `LineStyle`, `FillForegnd` など)を操作すると、含まれるすべての図形に対して一括で変更が適用される。
これにより、ループ処理を書くことなく、シェイプ群の属性変更をアトミック(不可分)に実行可能となる。
—
結び
Visio VBAにおける真のエンジニアリングとは、「Visioに余計な仕事をさせないこと」に他ならない。
UIとロジックを完全に分離し、メモリ上で構築したオブジェクト群を最小限のインタラクションでVisioエンジンに流し込む。この思想を身につけた時、あなたの書くVBAコードは、レガシーなスクリプトから、堅牢でエンタープライズな「システムコンポーネント」へと昇華する。
