【Visio VBA極限解説】Shape.CreateSelectionでレイヤー図形を一網打尽!高精度バッチエクスポートの全貌
開発現場でよくある要求だ。「特定のレイヤー(例えば『配線図』や『設備レイヤー』など)に属する図形だけを抜き出して、高解像度の画像として別ファイルに吐き出したい」。
これをマウス操作でやろうものなら、レイヤーの表示・非表示を切り替え、全選択して、不要なものを除外して……と、ヒューマンエラーの温床になる手作業のオンパレードだ。当然、我々エンジニアが目指すべきは、この一連のプロセスを1秒の迷いもなく完遂する完全自動化バッチの構築である。
しかし、Visio VBAのオブジェクトモデルを甘く見ていると痛い目をみる。`ActiveWindow.Select` や `DeselectAll` といったUI(画面)に依存した安易なコードを書いていないか?
今回は、Visioの描画エンジンを直に叩き、UIを一切汚さずにメモリ上で高速処理を行う `Shape.CreateSelection` を駆使した、実務直結の堅牢なレイヤー一括エクスポート・アーキテクチャを伝授する。
—
1. なぜ「画面選択(UI操作)」のVBAは地獄絵図になるのか?
初学者が陥る最大の罠が、次のようなコードだ。
‘ 【アンチパターン】絶対にやってはいけない実装
ActiveDocument.Pages(1).Shapes(1).Select visSelect
‘ 画面上のカーソルやウィンドウ状態に依存するため、
‘ 画面描画のチラつき、予期せぬフォーカス外れ、処理速度の著しい低下を招く
Visioの `Window` オブジェクトや `Selection` オブジェクトは、「人間が画面操作するための代理人」に過ぎない。これをバックグラウンドのバッチ処理で酷使すると、画面描画(ScreenUpdating)のオーバーヘッドがモロに直撃し、数千個の図形を扱う大規模図面では処理が耐え難いほど遅くなる。さらに、別タスクがアクティブになった瞬間に選択処理がクラッシュする。
プロフェッショナルの解:`Page.CreateSelection` によるメモリ上での集合演算
Visioのバックエンドエンジンには、画面を一切描画・変更することなく、条件に合致する図形群のハンドルを瞬時にかき集めるメソッドが用意されている。それが `CreateSelection` だ。
これを用いれば、UIの選択状態を一切変更することなく、メモリ上だけで高速に「レイヤーに属する図形群のセレクション(Selectionオブジェクト)」を作り上げることができる。
—
2. 実装のキモ:`visSelTypeByLayer` の正確な使い方
`Page.CreateSelection` の引数にはいくつか種類があるが、特定レイヤーに絞り込む場合は `visSelTypeByLayer` を指定する。
ここで注意すべき仕様上の罠がある。`visSelTypeByLayer` を使う場合、第2引数(`Criterion`)には、レイヤーの「名前(String)」ではなく、レイヤーオブジェクト、あるいはレイヤーのインデックスを渡す必要がある(※VisioのバージョンやAPIの仕様によりレイヤー名文字列を受け付ける場合もあるが、レイヤーオブジェクトそのものを渡すのが最も確実でバグがない)。
レイヤーオブジェクトを確実に取得し、そこに属する図形を `Selection` として取得するフローは以下のようになる。
1. 対象ページの `Layer` コレクションから、目的の名前を持つレイヤーを探索する。
2. 該当レイヤーが存在すれば、`Page.CreateSelection(visSelTypeByLayer, visSelLayerMember, TargetLayer)` を実行する。
3. 返された `Selection` オブジェクトに対し、画像エクスポート(`Export` メソッド)を叩く。
—
3. プロダクションコード:レイヤー一括画像エクスポート・マクロ
以下のコードは、エラーハンドリング、画面描画の完全抑制、リソースのクレンジングを網羅した、現場でそのまま使える実用モジュールだ。
Option Explicit
‘ ==============================================================================
‘ 概要: 指定レイヤーに属する図形のみを高解像度画像として一括エクスポートする
‘ 著者: チーフアーキテクト
‘ ==============================================================================
Public Sub ExportLayerToImageBatch()
‘ — 設定値 —
Const TARGET_LAYER_NAME As String = “設備配線” ‘ 抽出したいレイヤー名
Const OUTPUT_FILE_PATH As String = “C:\VisioOutput\ExportedLayer.png” ‘ 出力先パス
Const RENDER_DPI As Integer = 300 ‘ 出力解像度 (DPI)
‘ ————–
Dim targetPage As Visio.Page
Dim targetLayer As Visio.Layer
Dim targetSelection As Visio.Selection
Dim layerFound As Boolean
‘ パフォーマンスと安定性のための定石
Dim originalScreenUpdating As Boolean
originalScreenUpdating = Application.ScreenUpdating
On Error GoTo ErrorHandler
‘ 1. 画面描画とアラートを完全停止(爆速化の基本)
Application.ScreenUpdating = False
Application.alertsEnabled = False
‘ アクティブページの取得
Set targetPage = ActivePage
If targetPage Is Nothing Then
MsgBox “アクティブなページが存在しません。”, vbCritical, “エラー”
GoTo Finally
End If
‘ 2. 対象レイヤーの存在確認とオブジェクト取得
layerFound = False
Dim l As Visio.Layer
For Each l In targetPage.Layers
If l.Name = TARGET_LAYER_NAME Then
Set targetLayer = l
layerFound = True
Exit For
End If
Next l
If Not layerFound Then
MsgBox “指定されたレイヤー ‘” & TARGET_LAYER_NAME & “‘ がアクティブページに見つかりません。”, vbExclamation, “警告”
GoTo Finally
End If
‘ 3. 【核心】レイヤーに属する図形群をUI非依存で一括セレクション化
‘ 第1引数: 選択タイプ (レイヤー単位)
‘ 第2引数: 条件フラグ (レイヤーのメンバー)
‘ 第3引数: 条件値 (対象のLayerオブジェクト)
Set targetSelection = targetPage.CreateSelection(visSelTypeByLayer, visSelLayerMember, targetLayer)
‘ 4. 該当図形が存在するかチェック
If targetSelection.Count = 0 Then
MsgBox “レイヤー ‘” & TARGET_LAYER_NAME & “‘ に属する有効な図形が見つかりませんでした。”, vbInformation, “情報”
GoTo Finally
End If
‘ 5. 選択された図形群を一括エクスポート
‘ 注意: SelectionオブジェクトのExportメソッドは指定範囲のみを画像化する
‘ 解像度を指定するためには、事前にDocumentまたはExport設定を調整するか、
‘ Visioのフィルタ設定を利用する。ここでは標準的なExportを実行。
‘ 出力フォルダの存在確認(簡易チェック)
Call EnsureDirectoryExists(OUTPUT_FILE_PATH)
‘ エクスポート実行
targetSelection.Export OUTPUT_FILE_PATH
MsgBox “レイヤー画像のエクスポートが正常に完了しました。” & vbCrLf & _
“出力先: ” & OUTPUT_FILE_PATH & vbCrLf & _
“対象図形数: ” & targetSelection.Count & “個”, vbInformation, “完了”
Finally:
‘ 6. クリーンアップと状態復元
If Not targetSelection Is Nothing Then Set targetSelection = Nothing
If Not targetLayer Is Nothing Then Set targetLayer = Nothing
If Not targetPage Is Nothing Then Set targetPage = Nothing
Application.ScreenUpdating = originalScreenUpdating
Application.alertsEnabled = True
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error 0x” & Hex(Err.Number) & “: ” & Err.Description, vbCritical, “致命的エラー”
Resume Finally
End Sub
‘ ==============================================================================
‘ 補助関数: 出力先ディレクトリの存在チェックと自動生成
‘ ==============================================================================
Private Sub EnsureDirectoryExists(ByVal filePath As String)
Dim fso As Object
Dim folderPath As String
Set fso = CreateObject(“Scripting.FileSystemObject”)
folderPath = fso.GetParentFolderName(filePath)
If Not fso.FolderExists(folderPath) Then
fso.CreateFolder folderPath
End If
Set fso = Nothing
End Sub
—
四の五の言わず、まずはこのコードをVBAエディタ(`Alt + F11`)に貼り付け、レイヤー名とパスを自身の環境に合わせて実行してみてほしい。その圧倒的な処理速度と、画面が微動だにせず裏で仕事が終わるスマートさに唸るはずだ。
4. プロジェクトリーダーからの実践的アドバイス
実務でこの仕組みを組み込む際、さらに次の2点に気を配ると「プロの仕事」になる。
1. エクスポート時の解像度(DPI)の制御
Visioの `Selection.Export` は、デフォルトではVisioのページ設定や環境設定に依存する。もし厳密に高解像度(例: 300DPI固定など)で切り出したい場合は、エクスポート直前にレジストリやVisioの `Application.Settings`(または `ExportFilters`)を操作するか、一度一時ファイルに書き出す際のフィルタ引数を調整する必要がある。運用要件に応じてここをチューニングしてほしい。
2. データベースや外部システム連携
このバッチを基点にして、生成した画像パスをSQL ServerやSQLiteなどのDBに登録し、「どの図面のどのレイヤーがいつ更新されたか」の構成管理システムへ組み込むことも容易だ。UIに依存しないロジックを作っておけば、VBAから外向きのCOMコンポーネントや外部EXEからのヘッドレス実行へスケールアップする際も、コードを1行も書き直す必要がなくなる。
オブジェクトのライフサイクルを理解し、無駄なUI描画を排したコードこそが、あなたの業務を救う。現場の自動化を、次のステージへと引き上げてくれ。
