【Visio VBA】Shape.LayerMembershipを極める:マルチレイヤーの罠を排し、数万個の図形をミリ秒で制御するレイヤー一括管理術
実務で複雑な図面(システム構成図、プラント配管図、オフィスレイアウトなど)をVisioで自動生成・制御する際、避けて通れないのが「レイヤー(Layer)の制御」です。
「特定のベンダーの機器だけを非表示にしたい」
「編集してほしくない特定のインフラ配線図形だけをロックしたい」
こうした要件に対し、多くの開発者が「図形(Shape)を1つずつループで回し、プロパティを書き換える」という泥臭いコードを書き、そして絶望的な処理速度の遅さに頭を抱えます。あるいは、図形が複数のレイヤーに所属している(マルチレイヤー)場合の挙動を考慮せず、意図しない図形まで画面から消し去ってしまうバグを埋め込んでしまいます。
本記事では、Visio VBAの内部アーキテクチャ(ShapeSheetとレイヤーの関係)を解き明かし、極限まで高速化され、かつバグを寄せ付けない堅牢なレイヤー制御ロジックを伝授します。
—
1. なぜあなたのVisioマクロは遅いのか? レイヤー制御の「本質」を理解する
Visioにおけるレイヤーは、IllustratorやCADのような「階層化された透明なフィルム」とは少し挙動が異なります。
Visioのレイヤーの実態は、「ページ(Page)が保持するインデックス」と「図形(Shape)のShapeSheet内のLayer Membershipセルの値」の論理的なマッピングに過ぎません。
1-1. ShapeSheetにおける「LayerMembership」の構造
すべてのShapeは、内部のShapeSheetに `Layer Member` というセルを持っています。
ここには、その図形が所属するページのレイヤーインデックスが「セミコロン区切りの文字列(例: `”0;2″`)」などで格納されています。
- Page.Layers: ページに存在するレイヤーのリスト(1始まりのインデックス)。
- Shape.LayerCount: そのShapeが現在所属しているレイヤーの数。
- Shape.Layer(Index): Shapeが属する特定のLayerオブジェクトを返す。
1-2. アンチパターン:Shapeをループして「非表示」にする愚
最もやってはいけないのが、以下のコードです。
‘ 【アンチパターン】絶対に真似してはいけない、極めて低速なコード
Dim shp As Visio.Shape
For Each shp In ActivePage.Shapes
If shp.CellExists(“User.Category”, visExistsAnywhere) Then
If shp.Cells(“User.Category”).ResultStr(“”) = “Network” Then
‘ 図形のVisibleセルを直接書き換える、または泥臭くレイヤーから削除する
shp.Cells(“Geometry1.NoShow”).FormulaU = “TRUE” ‘ 描画パフォーマンス最悪
End If
End If
Next shp
なぜこれが非効率なのか?
1. 描画エンジンの頻繁なトリガー: Shapeのセルを個別に書き換えるたびに、Visioは画面の再描画と接続関係の再計算を行います。
2. マルチレイヤーの無視: 1つのShapeが「ネットワーク機器」かつ「一階フロア」という2つのレイヤーに属している場合、単純にVisible属性をいじると、他のレイヤーでの表示管理と矛盾が生じます。
1-3. プロフェッショナルのアプローチ
正しいアプローチは、「図形側の所属レイヤー(LayerMembership)を整理・確定させた上で、ページ側のLayerオブジェクトのプロパティ(Visible / Lock)を操作する」ことです。これを行えば、Visioのネイティブな描画C++エンジンが一瞬で描画を切り替えるため、VBAのループ処理によるオーバーヘッドはほぼゼロになります。
—
2. 堅牢な設計:マルチレイヤー(複数所属)を考慮したアーキテクチャ
実務の図面では、1つのShapeが複数のレイヤーに所属することが多々あります。
例えば、下図のような設計です。
- Shape A: 「ネットワーク機器」レイヤー & 「3階フロア」レイヤー
- Shape B: 「サーバー」レイヤー & 「3階フロア」レイヤー
ここで「ネットワーク機器」レイヤーを非表示にしたとき、Shape Aは非表示にするべきですが、Shape Bは表示したままにする必要があります。
これを制御するために、VBAから安全にレイヤーの所属(`LayerMembership`)を割り当て、ページのレイヤー設定を一括制御するクラス/モジュール設計を導入します。
レイヤー名操作における「フォールトトレラント」設計
レイヤーを操作する際、「レイヤー名がドキュメント内に存在しない」エラーや、「大文字・小文字・全角半角の揺れ」によるバグが多発します。
プロトタイプではない本番用コードでは、必ず以下のガードロジックを実装します。
1. 指定されたレイヤーが存在するか確認し、なければ動的に生成する。
2. レイヤーの存在確認は、インデックスではなく「不変のローカル名(NameU)」または「表示名(Name)」で行う。
3. 処理開始前に `Application.ScreenUpdating` や `DeferRecalc` を無効化し、処理終了後に `Finally` ブロックで確実に復元する。
—
3. 実装:一括レイヤー制御クラスライブラリ
以下に、実務でそのまま利用できる、極めて堅牢で高速なレイヤー管理モジュールのコードを示します。
このコードは、エラーハンドリング、Undo/Redoスコープの最適化、画面更新抑制、そしてマルチレイヤー対応をすべてクリアしています。
標準モジュール:`Mod_LayerController`
Attribute VB_Name = “Mod_LayerController”
Option Explicit
”’
”’
”’ 対象のページオブジェクト
”’ 制御対象のレイヤー名(NameUまたはName)
”’ 表示するかどうか (True: 表示, False: 非表示)
”’ ロックするかどうか (True: ロック, False: 編集可能)
Public Sub SetLayerState( _
ByVal targetPage As Visio.Page, _
ByVal layerName As String, _
ByVal isVisible As Boolean, _
ByVal isLocked As Boolean)
On Error GoTo ErrorHandler
‘ 1. ガードロジック
If targetPage Is Nothing Then Err.Raise 91, , “対象ページが設定されていません。”
If Trim(layerName) = “” Then Err.Raise 5, , “レイヤー名が空です。”
‘ 2. Visioの描画・再計算・Undoを抑制して高速化
Dim app As Visio.Application
Set app = targetPage.Application
Dim originalScreenUpdating As Boolean
Dim originalDeferRecalc As Boolean
originalScreenUpdating = app.ScreenUpdating
originalDeferRecalc = app.DeferRecalc
app.ScreenUpdating = False
app.DeferRecalc = True
‘ Undoスコープの開始(一括で元に戻せるようにする)
Dim undoScopeID As Long
undoScopeID = app.BeginUndoScope(“Set Layer State: ” & layerName)
‘ 3. レイヤーの取得(存在しない場合は作成する)
Dim targetLayer As Visio.Layer
Set targetLayer = GetOrCreateLayer(targetPage, layerName)
‘ 4. レイヤーセルの制御(ShapeSheetを直接操作するのと同じネイティブ高速処理)
‘ Layer.CellsCプロパティを使用して、VisibleおよびLockセルを書き換える
‘ visLayerVisible = 0, visLayerLock = 1
Dim visibleCell As Visio.Cell
Dim lockCell As Visio.Cell
Set visibleCell = targetLayer.CellsC(visLayerVisible)
Set lockCell = targetLayer.CellsC(visLayerLock)
‘ FormulaUに直接設定(1 = True / 0 = False)
visibleCell.FormulaU = IIf(isVisible, “1”, “0”)
lockCell.FormulaU = IIf(isLocked, “1”, “0”)
CleanUp:
‘ 5. 状態の復元とUndoスコープの終了
If Not app Is Nothing Then
If undoScopeID <> 0 Then app.EndUndoScope undoScopeID, True
app.ScreenUpdating = originalScreenUpdating
app.DeferRecalc = originalDeferRecalc
End If
Exit Sub
ErrorHandler:
Dim errDescription As String
errDescription = Err.Description
‘ 異常終了時はUndoをキャンセル(ロールバック)
If Not app Is Nothing And undoScopeID <> 0 Then
app.EndUndoScope undoScopeID, False
End If
‘ 呼び出し元へ再スロー
Err.Raise Err.Number, “Mod_LayerController.SetLayerState”, “レイヤー制御中にエラーが発生しました: ” & errDescription
End Sub
”’
”’
Private Function GetOrCreateLayer(ByVal targetPage As Visio.Page, ByVal layerName As String) As Visio.Layer
Dim lyr As Visio.Layer
‘ 既存のレイヤーを検索(NameU/Name両対応)
For Each lyr In targetPage.Layers
If StrComp(lyr.NameU, layerName, vbTextCompare) = 0 Or _
StrComp(lyr.Name, layerName, vbTextCompare) = 0 Then
Set GetOrCreateLayer = lyr
Exit Function
End If
Next lyr
‘ 存在しない場合は新規追加
Set GetOrCreateLayer = targetPage.Layers.Add(layerName)
End Function
”’
”’
Public Sub AssignShapeToLayer( _
ByVal shp As Visio.Shape, _
ByVal layerName As String, _
ByVal preserveOtherLayers As Boolean)
Dim targetPage As Visio.Page
Set targetPage = shp.ContainingPage
Dim targetLayer As Visio.Layer
Set targetLayer = GetOrCreateLayer(targetPage, layerName)
‘ preserveOtherLayers が False の場合、一度すべてのレイヤーから離脱させる
If Not preserveOtherLayers Then
Dim i As Integer
‘ LayerCountは変動するため、後ろからループして削除
For i = shp.LayerCount To 1 Step -1
shp.Layer(i).Remove shp, 0 ‘ 0 = visDeleteNoShapes (図形自体は削除しない)
Next i
End If
‘ レイヤーに追加
targetLayer.Add shp, 0 ‘ 0 = visAddNoRecurse (グループ図形の場合、子要素まで再帰的に追加しない場合は0、再帰的に追加する場合は visAddRecurse)
End Sub
—
4. ユースケース:データベースや設定ファイルと連動した一括制御
実務において、レイヤーの表示非表示ルールは「ハードコーディング」するべきではありません。
Excelの管理表やDB、あるいは定義ファイル(JSON/XML等)から条件を読み込み、動的に制御するのがスマートな設計です。
以下は、「図形のカスタムプロパティ(ShapeSheet内のProp.Category)の値に基づいて、自動的にレイヤーに振り分け、特定のカテゴリだけを一括非表示にする」 という統合シナリオの実装例です。
Attribute VB_Name = “Demo_Execution”
Option Explicit
”’
”’
Public Sub Run_LayerClassificationDemo()
Dim targetPage As Visio.Page
Set targetPage = Visio.ActivePage
If targetPage Is Nothing Then
MsgBox “アクティブなページが見つかりません。”, vbCritical
Exit Sub
End If
On Error GoTo ErrorHandler
Dim shp As Visio.Shape
‘ 1. 全図形を走査し、カスタムプロパティ「Prop.Category」の値に応じてレイヤーを再割り当て
‘ (実務ではDBや設定テーブルからこのマッピングを取得する)
For Each shp In targetPage.Shapes
‘ カスタムプロパティ “Prop.Category” を持っているか判定
If shp.CellExistsU(“Prop.Category”, visExistsAnywhere) Then
Dim categoryValue As String
categoryValue = shp.CellsU(“Prop.Category”).ResultStr(“”)
If categoryValue <> “” Then
‘ カテゴリ名と同名のレイヤーに所属させる(既存の他レイヤーへの所属は維持)
Mod_LayerController.AssignShapeToLayer shp, categoryValue, True
End If
End If
Next shp
‘ 2. 特定のレイヤーのみを非表示かつ編集不可(ロック)にする
‘ 例:ネットワーク構成図のうち「Internal_Network」は表示、「External_Security」は非表示にする
Mod_LayerController.SetLayerState targetPage, “External_Security”, False, True
Mod_LayerController.SetLayerState targetPage, “Internal_Network”, True, False
MsgBox “レイヤーの一括制御が完了しました。”, vbInformation, “成功”
Exit Sub
ErrorHandler:
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “エラー”
End Sub
—
5. アーキテクトが語る、データベース・ファイル連携時の注意点
実務レベルのシステムとしてこのコードを統合する場合、以下の点に深く留意してください。
① 外部ファイル(Excel/JSON)での「レイヤーマスタ」管理
レイヤー名や、どのレイヤーをデフォルトで「非表示」「ロック」にするかというメタデータは、コード内に埋め込まずにExcelの別シートや外部設定ファイル(XML/JSON)で定義してください。
図面テンプレートの更新時に、「VBAコードに触らずに、非表示対象のレイヤーを追加・変更できる」状態にしておくことが、長期的な保守コストを劇的に下げます。
② グループ図形(Group Shape)のメンバシップ問題
Visioで最もバグの原因になりやすいのが「グループ図形」です。
グループ化されたShapeに対して `Layer.Add` を行う際、第二引数のフラグに注意してください。
- `0` (または `visAddNoRecurse`): グループの「親」だけがレイヤーに属します。
- `1` (または `visAddRecurse`): グループ内のすべての「子」図形も同じレイヤーに属します。
もし、グループ内の一部の図形だけを別レイヤーで個別制御したい場合、`visAddRecurse` を使ってしまうと、親のレイヤー設定に引きずられて子のレイヤー設定が破壊されます。基本的には、明確な意図がない限り、グループ親のみを制御するか、設計ルールを厳格に統一する必要があります。
—
6. まとめ
Visio VBAにおけるレイヤー制御は、ただの「見た目の切り替え」に留まりません。
大規模なシステム構成図や設備管理図面において、「必要な情報だけを瞬時に抽出し、動的にCADライクなビューを生成する」ための強力なバックエンドエンジンです。
- ShapeSheetの構造(Layer Membership)を意識すること
- Shapeを個別に非表示にするのではなく、Page.Layers のプロパティを一括操作すること
- ScreenUpdating / DeferRecalc / UndoScope の3種の神器で、ミリ秒単位まで高速化すること
この3原則を徹底することで、あなたの作成するVisio自動化ツールは、他の開発者が作った「重くて不安定なマクロ」を遥かに凌駕する、真の「プロ仕様プロダクト」へと進化します。ぜひ、現場のコードに組み込んでその圧倒的なパフォーマンスを体感してください。
