レイヤー管理の迷宮を脱せよ: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におけるレイヤー管理のベストプラクティスの一つだ。ハードコードの誘惑に打ち勝ち、データとロジックを分離せよ。それが、伝説的なアーキテクトへの第一歩である。
