単一シェイプを複数レイヤーに重複所属させる:Visio VBAにおける多対多レイヤー制御の極意
多くの開発者が、Visioのレイヤー(Layer)をIllustratorや一般的なCADソフトと同等に捉えて大火傷を負います。
「1つの図形は、ただ1つのレイヤーに属する」という固定観念は、Visioにおいては通用しません。
Visioのオブジェクトモデルにおいて、レイヤーとシェイプの関係は「多対多(M:N)」です。1つのシェイプが「電気系統レイヤー」と「防災設備レイヤー」の双方に同時に所属し、表示・非表示やロック状態を個別に制御される――これこそが、Visioを高度なインテリジェントダイアグラムツールたらしめる真のパワーです。
しかし、この強力な仕様も、VBAから無計画に制御しようとすると、破綻したコードと原因不明の実行時エラーの温床となります。本記事では、この多対多構造を安全かつ高速に制御するための「プロ仕様の設計思想」と、そのまま実務に投入できる「堅牢なプロダクションコード」を伝授します。
—
1. Visioのレイヤー構造における「非対称性」の罠
VBAでレイヤーを制御する際、最初に直面するのがオブジェクトモデルの「アクセスの非対称性」です。
所属の確認は「Shape」、所属の変更は「Layer」
あるシェイプがどのレイヤーに属しているかを調べるには、`Shape` オブジェクトからアプローチします。
- `Shape.LayerCount`:シェイプが現在所属しているレイヤーの総数(読み取り専用)
- `Shape.Layer(Index)`:所属している `Layer` オブジェクトを取得(1から始まるインデックス)
しかし、「このシェイプを特定のレイヤーに所属させたい」、あるいは「脱退させたい」と考えたとき、`Shape` オブジェクトにはそれを直接操作するメソッド(例えば `Shape.Layers.Add` のようなもの)が存在しません。
操作はすべて、`Layer` オブジェクト側から行う必要があります。
- 所属させる:`Layer.Add(Shape, fRetainFormulas)`
- 脱退させる:`Layer.Remove(Shape, fRetainFormulas)`
【取得・参照】
Shape ──(LayerCount / Layer(i))──> Layer オブジェクト
【追加・削除】
Layer ──(Add / Remove)───────────> Shape オブジェクト
この非対称性を理解していないと、「Shapeのプロパティをいじってレイヤーを切り替えようとしたが、プロパティが読み取り専用で書き込めない」というビギナーの壁にぶつかることになります。
—
2. 堅牢な設計アプローチ:3つの鉄則
実務で耐えうるツールを開発するにあたり、以下の3つの鉄則をアーキテクチャに組み込みます。
鉄則①:名前解決の遅延評価と自動生成
「指定された名前のレイヤーが存在する」ことを前提にしてはいけません。ユーザーが手動でレイヤーを削除している可能性があります。
レイヤーにシェイプを割り当てる際は、必ず「存在チェックを行い、存在しなければオンデマンドで生成する」ラッパー関数を介します。
鉄則②:ShapeSheetの「LayerMember」セルの特性を知る
Visioのシェイプは、内部的にはShapeSheetの「LayerMember」セルでレイヤー所属を管理しています。VBAから `Layer.Add` を呼び出すと、このセルにレイヤーのインデックス(0から始まる半角スペース区切りの数値文字列、例: `”0 2″`) が書き込まれます。
直接ShapeSheetを書き換える手法(`Cell.FormulaU` の操作)は高速ですが、インデックスの整合性を失うリスクがあるため、VBAでは `Layer.Add` / `Layer.Remove` メソッドを使用するのが最も安全です。
鉄則③:再計算と描画のグローバル抑制
何百ものシェイプに対して動的に複数レイヤーへの割り当てを行うと、1回操作するたびにVisioの描画エンジンとShapeSheetの再計算が走り、動作が著しく低下します。
処理の前後で必ず `Application.ScreenUpdating` と `Application.DeferRecalc` を制御してください。これだけで処理速度が10倍以上変わります。
—
3. 極限のプロダクションコード:複数レイヤー割り当てモジュール
以下に、実務で即座に使える堅牢なVBAコードを示します。
このコードは、指定された単一のシェイプに対し、複数のレイヤー(カンマ区切り等で指定)への重複所属を安全に実行します。既存の所属レイヤーを維持するか、あるいはすべてクリアして再割り当てするかの制御も可能です。
Option Explicit
”’
”’
”’ 対象のShapeオブジェクト
”’ 所属させるレイヤー名の配列(例: Array(“電気配線”, “機械配置”))
”’ Trueの場合、既存のレイヤー所属をすべて解除してから新規割り当てします。
Public Sub AssignShapeToLayers( _
ByVal targetShape As Visio.Shape, _
ByRef layerNames() As String, _
Optional ByVal replaceExisting As Boolean = False)
Dim targetPage As Visio.Page
Set targetPage = targetShape.ContainingPage
‘ 描画・計算抑制によるパフォーマンス最適化
Dim originalScreenUpdating As Boolean
Dim originalDeferRecalc As Boolean
originalScreenUpdating = Visio.Application.ScreenUpdating
originalDeferRecalc = Visio.Application.DeferRecalc
Visio.Application.ScreenUpdating = False
Visio.Application.DeferRecalc = True
On Error GoTo ErrorHandler
‘ 1. 既存レイヤーのクリア処理(要求された場合)
If replaceExisting Then
Dim i As Long
‘ Layer.Removeを実行するとShape.LayerCountが減少するため、後ろからループする
For i = targetShape.LayerCount To 1 Step -1
Dim currentLayer As Visio.Layer
Set currentLayer = targetShape.Layer(i)
‘ fRetainFormulas = 0 (セルの数式を保持しない。通常は0で安全)
currentLayer.Remove targetShape, 0
Next i
End If
‘ 2. 指定された各レイヤーへの割り当て
Dim nameIndex As Long
For nameIndex = LBound(layerNames) To UBound(layerNames)
Dim targetLayerName As String
targetLayerName = Trim$(layerNames(nameIndex))
If Len(targetLayerName) > 0 Then
‘ レイヤーの取得(存在しなければ自動生成)
Dim targetLayer As Visio.Layer
Set targetLayer = GetOrCreateLayer(targetPage, targetLayerName)
‘ シェイプをレイヤーに追加 (fRetainFormulas = 0)
targetLayer.Add targetShape, 0
End If
Next nameIndex
CleanExit:
‘ 描画・計算設定の復元
Visio.Application.ScreenUpdating = originalScreenUpdating
Visio.Application.DeferRecalc = originalDeferRecalc
Exit Sub
ErrorHandler:
‘ エラー発生時のロールバックと通知
Dim errDesc As String
errDesc = Err.Description
On Error Resume Next ‘ 復元処理中のエラーを無視
Visio.Application.ScreenUpdating = originalScreenUpdating
Visio.Application.DeferRecalc = originalDeferRecalc
Err.Raise Err.Number, “AssignShapeToLayers”, “レイヤー割り当て中にエラーが発生しました: ” & errDesc
End Sub
”’
”’
Private Function GetOrCreateLayer(ByVal targetPage As Visio.Page, ByVal layerName As String) As Visio.Layer
On Error Resume Next
Dim targetLayer As Visio.Layer
‘ レイヤー名による直接取得を試みる
Set targetLayer = targetPage.Layers.Item(layerName)
On Error GoTo 0
‘ 存在しなかった場合は新規作成
If targetLayer Is Nothing Then
Set targetLayer = targetPage.Layers.Add(layerName)
End If
Set GetOrCreateLayer = targetLayer
End Function
このコードを呼び出すクライアント側の実装例
例えば、Excelの機器リストや外部データベースから「機器ID」「機器名」「所属レイヤー群」を取得し、それらをVisio上のシェイプに反映させるメイン処理は以下のようになります。
Public Sub ExecLayerMappingDemo()
Dim activePage As Visio.Page
Set activePage = Visio.ActivePage
If activePage Is Nothing Then
MsgBox “アクティブなページが見つかりません。”, vbCritical
Exit Sub
End If
‘ 選択されているシェイプを対象にする
Dim selection As Visio.Selection
Set selection = Visio.ActiveWindow.Selection
If selection.Count = 0 Then
MsgBox “シェイプを選択してから実行してください。”, vbExclamation
Exit Sub
End If
Dim targetShape As Visio.Shape
Set targetShape = selection.PrimaryItem
‘ 例:この機器は「動力系統」であり、かつ「1階平面図」の要素でもある
Dim myLayers() As String
ReDim myLayers(1)
myLayers(0) = “01_PowerSystem”
myLayers(1) = “02_FirstFloor”
‘ 既存の所属をクリアして、上記2つのレイヤーに重複所属させる
On Error GoTo ErrorHandler
AssignShapeToLayers targetShape, myLayers, replaceExisting:=True
MsgBox “シェイプ「” & targetShape.Name & “」を以下のレイヤーに割り当てました:” & vbCrLf & _
Join(myLayers, “, “), vbInformation, “処理完了”
Exit Sub
ErrorHandler:
MsgBox Err.Description, vbCritical, “エラー”
End Sub
—
4. 外部データ(Excel/DB)連携時の設計思想
エンタープライズ開発において、Visioのレイヤー情報はローカルの図面内だけで完結しません。Excelのシステム系統図データや、RDBMS(SQL Server / PostgreSQL等)内の資産管理テーブルと同期させるケースが多々あります。
このとき、データベース設計とVisioのオブジェクトモデルを美しくマッピングさせるための設計思想が重要になります。
データモデルにおける「多対多」の解決
データベースにおいて、シェイプ(機器)とレイヤーは以下のような交差テーブル(中間テーブル)で表現されます。
[Shapes Table] 1 ──── N [Shape_Layers] N ──── 1 [Layers Table]
- ShapeID (PK) – ShapeID (FK) – LayerID (PK)
- ShapeName – LayerID (FK) – LayerName
これをVisio VBAで同期する際、もっとも避けるべきなのは「DBからレコードを1件読み込むたびに、Visioのレイヤーを都度検索・作成する」という設計です。ネットワークおよびCOM(Component Object Model)の通信オーバーヘッドにより、数千件のデータでフリーズを引き起こします。
同期処理を高速化する3ステップアーキテクチャ
1. レイヤーキャッシュの構築(一括取得)
同期処理の開始時に、現在のVisioページに存在する全レイヤーの「名前とオブジェクト」のペアを `Scripting.Dictionary` にキャッシュします。これにより、ループ内での `Layers.Item(name)` による名前解決(COM呼び出し)を排除します。
2. 一方向バルク処理
DBの交差エンティティをメモリ上で「ShapeID」をキーとするディクショナリ(値はレイヤー名の配列)に集約し、Visio側では各Shapeに対して一回だけ `AssignShapeToLayers` を呼び出します。
3. メタデータのShapeSheet埋め込み
レイヤー割り当てと同時に、ShapeSheetの「User-defined Cells(ユーザー定義セル)」に、所属レイヤーのカンマ区切りテキスト(例: `User.BelongingLayers = “01_PowerSystem,02_FirstFloor”`) を同期値として書き込んでおきます。これにより、次回同期時に「DBの値とVisioの値に差分があるか」を瞬時に判定でき、変更のないシェイプへの無駄なレイヤー再割り当て処理をスキップできます。
—
5. まとめ
Visioにおける複数レイヤーへの重複所属は、図面の「表現力」を劇的に引き上げる強力な武器です。
この機能をVBAで掌握するためには、以下の原則を常に念頭に置いてください。
- 「参照はShapeから、変更はLayerから」という非対称なAPI設計を理解すること。
- レイヤーの動的割り当て時は、自動生成(GetOrCreate)を挟み、ランタイムエラーを徹底的に排除すること。
- 大量処理時は、`ScreenUpdating` と `DeferRecalc` を抑制し、COM通信のオーバーヘッドを最小化すること。
この設計思想と堅牢なラッパー関数をあなたのライブラリに組み込めば、複雑なファシリティマネジメントや系統図自動生成システムの実装において、バグ知らずの圧倒的なパフォーマンスを実現できるでしょう。
