【テクニカル・上級編】【中級者向け】レイヤーの表示状態を「プリセット」として保存し、ボタン一つで表示を切り替えるUI構築 – Visio VBA解析バイブル

スポンサーリンク
スポンサーリンク

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 を厳格に管理し、メモリリークを根絶せよ。

これらを遵守することこそが、伝説的なアーキテクチャを構築するための唯一の道である。

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