Visio Layer State Orchestration: レイヤー表示状態の「状態遷移」を設計する
大規模なシステム構成図や複雑な配線図を扱うエンジニアにとって、Visioは単なる描画ツールではない。それは、膨大な情報を階層化して管理するための「情報のコンテナ」である。
しかし、多くの現場で見られる自動化スクリプトは、あまりに脆弱だ。「特定のレイヤーを表示する」「特定のレイヤーを隠す」といった命令を、場当たり的なループ処理で記述している。これでは、図面が複雑化し、レイヤー数が数十、数百と増大した際、パフォーマンスの低下や、予期せぬ描画エラー、さらにはメモリリークによるVisioのクラッシュを招くことになる。
本稿では、単なる「表示・非表示の切り替え」を超えた、「レイヤー状態のプリセット管理(State Management)」のアーキテクチャについて解説する。プロフェッショナルが取るべき、決定論的な状態制御と、Windows APIを駆使した描画最適化の極意を伝授しよう。
1. 設計思想:命令的制御から宣言的制御へ
初心者は「Aレイヤーを出し、Bレイヤーを消す」という命令的(Imperative)なコードを書く。これに対し、シニアエンジニアは「現在の状態を『配線モード』という状態(State)に遷移させる」という宣言的(Declarative)なアプローチを取る。
プリセット管理を実現するためには、以下の3層構造が必要である。
1. Persistence Layer(永続化層): プリセット定義(どのレイヤーがONか)をどこに保存するか。
2. Logic Layer(ロジック層): 定義に基づき、現在の図面状態を計算・適用するエンジン。
3. UI Layer(UI層): ユーザーが状態を選択するためのインターフェース。
データの保存先として、外部ファイル(JSON/XML)を用いる手法もあるが、Visioドキュメントのポータビリティを考慮すれば、「ドキュメントのカスタムプロパティ」または「非表示のシェイプに格納されたデータ」に定義を持たせるのが、レガシー環境との親和性も高く、最も堅牢な設計となる。
2. 実装:State Manager クラスの構築
ここでは、`Scripting.Dictionary` を用いてメモリ上で状態を管理し、レイヤーの可視性を一括制御するコアロジックを実装する。
実装上の重要事項:
- COMオブジェクトのライフサイクル管理: `Set obj = Nothing` による明示的な解放。
- エラーハンドリング: レイヤーが存在しない場合の例外処理。
- 高速化: `CellsU` プロパティへのアクセス最小化。
' --- Module: LayerStateManager ---
' 参照設定: Microsoft Scripting Runtime (Dictionary用)
Option Explicit
' プリセットの定義構造体(概念)
' Key: プリセット名 (String)
' Value: Layerの可視性マップ (Dictionary: LayerName -> Boolean)
Public Class LayerStateManager
Private pDictionary As Scripting.Dictionary
Private pTargetPage As Visio.Page
Private Sub Class_Initialize()
Set pDictionary = New Scripting.Dictionary
End Sub
' プリセットの登録
' presetName: "WiringMode" 等
' layerMap: Dictionary(LayerName, True/False)
Public Sub RegisterPreset(ByVal presetName As String, ByRef layerMap As Scripting.Dictionary)
If Not pDictionary.Exists(presetName) Then
pDictionary.Add presetName, layerMap
End If
End Sub
' プリセットの適用
Public Sub ApplyPreset(ByVal presetName As String)
If Not pDictionary.Exists(presetName) Then
Err.Raise 5, "LayerStateManager", "Preset '" & presetName & "' not found."
End If
Dim targetMap As Scripting.Dictionary
Set targetMap = pDictionary(presetName)
' 描画更新の抑制(後述するAPI呼び出しと併用)
' ※VisioにはExcelのような完全なScreenUpdatingがないため、
' 描画制御はWindows APIで行うのがプロの作法である。
Dim layerKey As Variant
Dim layerName As String
Dim isVisible As Boolean
Dim vLayer As Visio.Layer
On Error GoTo ErrorHandler
' 描画停止の呼び出し
Call DisableRedraw(pTargetPage.Application.ActiveWindow)
For Each layerKey In targetMap.Keys
layerName = CStr(layerKey)
isVisible = CBool(targetMap(layerKey))
On Error Resume Next
Set vLayer = pTargetPage.LayerByVisioID(pTargetPage.Layers.Item(layerName).ID) ' 高速なIDアクセス
On Error GoTo ErrorHandler
If Not vLayer Is Nothing Then
' CellsUを使用し、ユニバーサルなプロパティ名で確実に制御
vLayer.CellsU("Visible").FormulaU = IIf(isVisible, "1", "0")
End If
Set vLayer = Nothing
Next layerKey
CleanUp:
' 描画再開の呼び出し
Call EnableRedraw(pTargetPage.Application.ActiveWindow)
Exit Sub
ErrorHandler:
MsgBox "Error applying preset: " & Err.Description, vbCritical
Resume CleanUp
End Sub
' ページオブジェクトのセット
Public Sub SetTargetPage(ByRef pg As Visio.Page)
Set pTargetPage = pg
End Sub
Private Sub Class_Terminate()
Set pDictionary = Nothing
Set pTargetPage = Nothing
End Sub
End Class
3. 極限の最適化:Windows API による描画フリッカーの制御
Visioのレイヤー操作は、一つひとつの変更が描画イベントをトリガーする。数百のレイヤーをループで回せば、画面は激しく点滅(フリッカー)し、ユーザー体験を著しく損なう。
これを防ぐため、`user32.dll` の `LockWindowUpdate` または `RedrawWindow` を使用して、アプリケーションの描画更新を物理レベルでロックする。これは、レガシーなWindows環境においても極めて有効な、アーキテクトだけが知る「外科手術」的な手法である。
' --- Module: WinAPI_Utils ---
Option Explicit
' Windows API 宣言
If VBA7 Then
Private Declare PtrSafe Function LockWindowUpdate Lib "user32" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function RedrawWindow Lib "user32" (ByVal hwnd As LongPtr, ByVal lpRect As LongPtr, ByVal lpRgn As LongPtr, ByVal AddFlags As Long) As Long
Else
Private Declare Function LockWindowUpdate Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function RedrawWindow Lib "user32" (ByVal hwnd As Long, ByVal lpRect As Long, ByVal lpRgn As Long, ByVal AddFlags As Long) As Long
End If
Private Const RDW_INVALIDATE As Long = &H1
Private Const RDW_ALLCHILDREN As Long = &H4
Private Const RDW_ERASE As Long = &H4
' 描画をロックする
Public Sub DisableRedraw(ByRef win As Visio.Window)
' Windowのハンドルを取得し、更新をロック
LockWindowUpdate win.Hwnd
End Sub
' 描画を強制的に再開・更新する
Public Sub EnableRedraw(ByRef win As Visio.Window)
' ロックを解除
LockWindowUpdate 0
' ウィンドウ全体を無効化し、再描画を要求
RedrawWindow win.Hwnd, 0, 0, RDW_INVALIDATE Or RDW_ALLCHILDREN Or RDW_ERASE
End Sub
4. 運用における「メモリの重み」への配慮
シニアエンジニアとして忘れてはならないのは、「Visioは長時間稼働するプロセスになりがちである」という事実だ。
業務自動化システムとして組み込まれた場合、VBAの変数がメモリ上に残り続けると、COM参照のカウントが減らず、Visioプロセスが「ゾンビ」化する。
- 明示的な解放: `Set obj = Nothing` を、ループの終わりだけでなく、エラーハンドラ内やクラスの `Terminate` イベントでも徹底すること。
- Dictionaryの再利用回避: 大規模なプリセットを扱う場合、使い終わった `Scripting.Dictionary` は速やかに破棄するか、再利用する場合は `RemoveAll` を呼び出し、メモリ空間をクリーンに保つこと。
結論
レイヤーの表示切り替えを「単なるプロパティ操作」と捉えるか、「状態遷移の制御」と捉えるか。この視点の差が、システムの堅牢性とスケーラビリティを決定づける。
1. State Management を導入し、ロジックとデータを分離せよ。
2. Windows API を使い、ユーザーにストレスを与えない描画制御を実現せよ。
3. Object Lifecycle を厳格に管理し、メモリリークを根絶せよ。
これらを遵守することこそが、伝説的なアーキテクチャを構築するための唯一の道である。
