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

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

レイヤー管理の迷宮を脱せよ:Visio VBAにおける「プリセット・スイッチング」の堅牢な設計論

現場の自動化ツール開発において、多くのエンジニアが陥る「初歩的かつ致命的なミス」がある。それは、「ロジックとデータを混同すること」だ。

例えば、「配線図モード」と「機器配置モード」を切り替える機能を実装しようとしたとき、君はこう書かないか?

' --- 典型的な「素人」のコード ---
Sub SwitchToWiringMode()
    ActivePage.Layers("WiringLayer").Cells("Visible").Formula = "1"
    ActivePage.Layers("EquipmentLayer").Cells("Visible").Formula = "0"
End Sub

もし、ユーザーがレイヤー名を「Wiring_Layer」に変更したら? あるいは、誤ってレイヤーを削除したら?
君の作ったツールは、即座に実行時エラーを吐いて停止し、現場の信頼を失う。これは「ツール」ではなく、ただの「壊れやすいスクリプト」だ。

本稿では、プロフェッショナルとして、「レイヤーの表示状態をプリセットとして定義し、動的に切り替える」ための、極めて堅牢でメンテナンス性の高いアーキテクチャを伝授する。

1. 設計思想:データ駆動型アプローチへの転換

プロの設計では、「どのレイヤーをどうするか」という情報をコード内にハードコード(直書き)しない。

代わりに、以下の3層構造で設計する。

1. State(状態): レイヤー名と表示状態(Visible/Hidden)のペア。
2. Storage(保存): 状態をファイル内に保持するメカニズム(外部ファイルに頼らない)。
3. Engine(実行): 状態を読み込み、エラーを回避しながらページに適用するロジック。

ここで重要なのは、「設定はVisioファイル自体に持たせる」という判断だ。外部のCSVやINIファイルに設定を逃がすと、ファイル共有時に「設定ファイルが見つかりません」という、自動化における最悪のトラブルを招く。Visioの`CustomDocumentProperties`を活用することで、ファイル単体で完結する「ポータブルな知能」を持たせることができる。

2. 実装:プロダクション・グレードのコード例

以下に、私が設計の現場で実際に使用するクラスモジュールに近い構成の標準モジュールを示す。このコードは、レイヤーの存在チェック、エラーハンドリング、そして設定の永続化を網羅している。

実装の肝:レイヤー・プリセット・マネージャー

Option Explicit

' ==============================================================================
' Module: modLayerManager
' Description: レイヤーの表示状態をプリセットとして保存・適用する堅牢なマネージャー
' ==============================================================================

''' <summary>
''' 指定したプリセット名を現在のレイヤー状態から保存する
''' </summary>
''' <param name="presetName">保存するプリセットの名前</param>
Public Sub SaveCurrentLayersAsPreset(ByVal presetName As String)
    Dim pg As Visio.Page
    Dim ly As Visio.Layer
    Dim stateString As String
    Dim doc As Visio.Document
    
    Set pg = ActivePage
    Set doc = ActiveDocument
    
    ' 1. 現在のレイヤー状態を「レイヤー名:状態」の形式で文字列化
    ' 例: "Wiring:1|Equipment:0|Text:1"
    For Each ly In pg.Layers
        stateString = stateString & ly.Name & ":" & ly.CellsU("Visible").Formula & "|"
    Next ly
    
    ' 末尾のパイプを削除
    If Len(stateString) > 0 Then
        stateString = Left(stateString, Len(stateString) - 1)
    End If
    
    ' 2. Documentのカスタムプロパティに保存(ファイル自体に情報を埋め込む)
    On Error Resume Next
    doc.CustomDocumentProperties(presetName).Delete
    On Error GoTo 0
    
    doc.CustomDocumentProperties.Add presetName, visDocPropText, stateString
    
    MsgBox "プリセット '" & presetName & "' を保存しました。", vbInformation
End Sub

''' <summary>
''' 保存されたプリセットを適用する
''' </summary>
''' <param name="presetName">読み込むプリセットの名前</param>
Public Sub ApplyPreset(ByVal presetName As String)
    Dim doc As Visio.Document
    Dim pg As Visio.Page
    Dim presetData As String
    Dim pairs() As String
    Dim pair As Variant
    Dim parts() As String
    Dim targetLayer As Visio.Layer
    Dim layerExists As Boolean
    
    Set doc = ActiveDocument
    Set pg = ActivePage
    
    ' 1. プロパティの存在確認
    On Error Resume Next
    presetData = doc.CustomDocumentProperties(presetName).Value
    If Err.Number <> 0 Then
        MsgBox "プリセット '" & presetName & "' が見つかりません。", vbCritical
        On Error GoTo 0
        Exit Sub
    End If
    On Error GoTo 0
    
    ' 2. 全レイヤーを一旦非表示にする(クリーンな状態からの適用)
    ' ※ 全レイヤーを消すと操作不能になるリスクがあるため、設計思想に応じて調整
    ' ここでは「プリセットに含まれるレイヤー以外は隠す」という挙動を目指す
    
    ' 3. 文字列のパースと適用
    pairs = Split(presetData, "|")
    
    ' 適用前に全レイヤーを一旦不可視にする(必要に応じて)
    ' ※ 注意:すべてのレイヤーを隠すと何も見えなくなるため、
    ' 実際には「プリセットに含まれないレイヤーをどう扱うか」のポリシーが必要
    
    For Each pair In pairs
        parts = Split(pair, ":")
        If UBound(parts) = 1 Then
            Dim lyName As String: lyName = parts(0)
            Dim lyVis As String: lyVis = parts(1)
            
            ' レイヤーの存在チェック(ここが重要:エラー回避の生命線)
            Set targetLayer = Nothing
            On Error Resume Next
            Set targetLayer = pg.Layers.Item(lyName)
            On Error GoTo 0
            
            If Not targetLayer Is Nothing Then
                ' Formula(文字列)としてセット
                targetLayer.CellsU("Visible").Formula = lyVis
            Else
                ' レイヤーが存在しない場合はログ出力または無視
                Debug.Print "Warning: Layer '" & lyName & "' not found. Skipping."
            End If
        End If
    Next pair
    
    Debug.Print "Preset '" & presetName & "' applied successfully."
End Sub

3. アーキテクトの視点:なぜこの設計なのか

このコードが、単なる「レイヤー切り替え」と何が違うのか。その設計意図を解説する。

① `CustomDocumentProperties` による自己完結性

多くの開発者は、設定を `Settings.ini` のような外部ファイルに書き出したがる。しかし、それは「配布」の観点で極めて脆弱だ。
`CustomDocumentProperties` を使えば、設定データはVisioファイル(.vsdx)のメタデータの一部となる。ユーザーがメールでファイルを送れば、設定も一緒に届く。この「ポータビリティ」こそが、業務ツールにおける真の堅牢性だ。

② 「存在しないレイヤー」への寛容さ

`pg.Layers.Item(lyName)` は、指定した名前のレイヤーが存在しない場合にランタイムエラーを発生させる。
プロのコードでは、必ず `On Error Resume Next` を用いてオブジェクトの取得を試み、`If Not targetLayer Is Nothing Then` で存在を確認してから操作を行う。この「ガード句」こそが、ユーザーにストレスを与えないための防波堤だ。

③ 文字列シリアライズによる柔軟なデータ構造

レイヤーの状態を `LayerName:Visibility|LayerName:Visibility` という形式の文字列にシリアライズ(直列化)している。これにより、レイヤーの数が増えても、データ構造を変えずに拡張できる。これは将来的に、レイヤーの「色」や「透明度」などの属性も追加したい場合に、`LayerName:Visibility:Color|…` と拡張するだけで対応できる、スケーラブルな設計だ。

4. 実践:UIへの組み込み

このエンジンを動かすには、Visioの「アドイン」としてボタンを配置するか、あるいは単純な「フォーム(UserForm)」を作成するのが定石だ。

UserFormのボタンに記述するコード例:

' 「配線図モード」ボタン
Private Sub btnWiringMode_Click()
    ' 事前に保存しておいたプリセットを呼び出すだけ
    modLayerManager.ApplyPreset "WiringMode"
End Sub

' 「現在の状態を保存」ボタン(管理者・作成者用)
Private Sub btnSaveCurrent_Click()
    Dim pName As String
    pName = InputBox("保存するプリセット名を入力してください")
    If pName <> "" Then
        modLayerManager.SaveCurrentLayersAsPreset pName
    End If
End Sub

まとめ:エンジニアとしての矜持

自動化ツールの価値は、「動くこと」ではない。「ユーザーがどのような操作ミスをしても、あるいは環境がどう変化しても、壊れずに動き続けること」にある。

今回紹介した「データ駆動型・自己完結型」の設計は、Visio VBAにおけるレイヤー管理のベストプラクティスの一つだ。ハードコードの誘惑に打ち勝ち、データとロジックを分離せよ。それが、伝説的なアーキテクトへの第一歩である。

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