【実務・中級編】Shape.ContainingPageおよびContainingMasterプロパティを使った配置先コンテキストに応じた動的処理の切り替え – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:Shapeの「居場所」を誤るな!ContainingPageとContainingMasterで実現する堅牢なコンテキスト判定

こんにちは、チーフアーキテクトの私だ。
これまで数々の巨大なプラント図面、ネットワークトポロジ、そして複雑な業務フローチャートを自動生成するVisioソリューションを構築してきた。その中で、開発者が最も陥りやすく、かつデバッグが困難なバグの一つが「マスターシェイプの編集時における誤動作」だ。

「図面上の全シェイプを走査してプロパティを書き換えるマクロ」を書いたことはないか?
そして、ステンシルを開いてマスターシェイプをダブルクリックし、その中で同じマクロを実行した途端、わけのわからない実行時エラーが発生して頭を抱えたことはないだろうか。

今回は、Visioのオブジェクトモデルの深層に切り込み、シェイプが今どこに存在するのか——実体ページ上なのか、それともマスターの内部なのかを完璧に識別し、プロダクション環境で絶対に破綻しない安全設計を伝授する。

1. なぜ「ページのつもりでシェイプを触る」とシステムが崩壊するのか?

Visio VBA初級者が最初に犯す致命的なミスは、`ActivePage` や `ActiveWindow.Page` を無条件に信用することだ。

通常、ユーザーが図面ページを見ている時、`ActivePage` はそのページを返す。しかし、ユーザーがステンシル内のマスターシェイプ(例えば「ルーター」のアイコンなど)の編集ウィンドウを開いている瞬間、世界が変わる。そのウィンドウのコンテキストは「ページ」ではなく「マスター(Masterオブジェクト)」の中なのだ。

この状態で、コードが `ActivePage` を前提に動いているとどうなるか?

  • 存在しない(あるいは意図しない)ページコンテキストにアクセスし、エラーが発生する。
  • マスターシェイプの中にある「定義用シェイプ」に対して、実ページ用の処理(座標の厳密な補正や、特定レイヤーへの割り当てなど)が走り、マスターの定義が破壊される。

マスターが破壊されると、そのステンシルを使用している全ドキュメントのインスタンスが破損するという、目も当てられない事態を引き起こす。プロのエンジニアであれば、この「コンテキストの迷子」をコードレベルで完全封鎖しなければならない。

2. 救世主:`ContainingPage` と `ContainingMaster` プロパティ

Visioの `Shape` オブジェクトには、自身がどこに属しているかを教えてくれる強力なプロパティが用意されている。それが `ContainingPage``ContainingMaster` だ。

この2つのプロパティの挙動を、骨の髄まで理解してほしい。

1. `Shape.ContainingPage`

  • シェイプが実体の「図面ページ(Page)」上に存在する場合、その `Page` オブジェクトを返す。
  • マスターシェイプ内にある場合、これは `Nothing` を返す。

2. `Shape.ContainingMaster`

  • シェイプが「マスター(Master)」の内部に存在する場合、その `Master` オブジェクトを返す。
  • 通常の図面ページ上にある場合、これは `Nothing` を返す。

つまり、あるシェイプに遭遇したとき、以下のシンプルな排他制御を行うだけで、そのシェイプが「実戦配備されているもの」か「工場(マスター)の設計図の中にあるもの」かを完璧に切り分けられるのだ。

If Not shp.ContainingPage Is Nothing Then
‘ 実ページ上の処理
ElseIf Not shp.ContainingMaster Is Nothing Then
‘ マスター編集中の処理(あるいは処理のスキップ)
End If

非常にシンプルだが、この判定をサボるかサボらないかで、あなたの書くVBAの「格」が決定的に変わる。

3. 【実務向けプロダクションコード】コンテキスト適応型シェイプ一括処理エンジン

現場でそのまま使える、堅牢なエラーハンドリング付きのコードテンプレートを提示する。
このプロシージャは、選択中のシェイプ、あるいはページ上の全シェイプを走査する際、今ユーザーがどこをいじっているのかを自動検知し、マスターを誤って汚染するのを完全に防ぐ設計になっている。

Option Explicit

Public Sub ExecuteContextAwareShapeProcess()
‘ ==============================================================================
‘ 処理概要:
‘ 選択中またはページ内のシェイプを安全に走査し、
‘ 現在地(実ページ vs マスター内部)に応じて処理を動的に切り替える。
‘ ==============================================================================

On Error GoTo ErrorHandler

Dim shp As Visio.Shape
Dim targetShapes As Visio.Selection
Dim processedCount As Long
Dim skippedCount As Long

processedCount = 0
skippedCount = 0

‘ 画面描画を停止してパフォーマンスを極限まで引き上げる
Application.ScreenUpdating = False
Application.UndoEnabled = False

‘ 選択されているシェイプを取得(何も選択されていなければアクティブウィンドウ全体)
Set targetShapes = ActiveWindow.Selection

If targetShapes.Count = 0 Then
‘ 選択がない場合は、アクティブなコンテキスト(ページまたはマスター)の全シェイプを対象とする
‘ ※ ここで ActivePage を安易に使うとマスター編集時にエラーになるため、Windowから引く
Dim currentWindowType As Integer
currentWindowType = ActiveWindow.Type

MsgBox “シェイプが選択されていません。処理を終了します。”, vbExclamation, “ガード発動”
GoTo CleanUp
End If

For Each shp in targetShapes
‘ ————————————————————————–
‘ コンテキスト判定の核心
‘ ————————————————————————–
If Not shp.ContainingPage Is Nothing Then
‘ 【ケースA】実図面ページ上のシェイプ
Call ProcessOnActualPage(shp)
processedCount = processedCount + 1

ElseIf Not shp.ContainingMaster Is Nothing Then
‘ 【ケースB】マスターシェイプ編集ウィンドウ内のシェイプ
‘ マスターの構造を壊さないよう、専用の処理を行うか、今回は安全のためスキップする
Call ProcessInsideMaster(shp)
skippedCount = skippedCount + 1

Else
‘ 【例外ケース】グループのネスト等で特殊な状態にある場合
‘ 通常はここに来ないが、ロバスト性のためにガードする
Debug.Print “Warning: Unknown context for Shape ID: ” & shp.ID
End If
Next shp

‘ 完了ログ
Application.ScreenUpdating = True
Application.UndoEnabled = True

MsgBox “処理が完了しました。” & vbCrLf & _
” – 実ページ処理数: ” & processedCount & vbCrLf & _
” – マスター内処理(スキップ): ” & skippedCount, vbInformation, “アーキテクチャ・ガード正常終了”
Exit Sub

ErrorHandler:
Application.ScreenUpdating = True
Application.UndoEnabled = True
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error 0x” & Hex(Err.Number) & “: ” & Err.Description, vbCritical, “致命的エラー”

CleanUp:
Set targetShapes = Nothing
Set shp = Nothing
End Sub

Private Sub ProcessOnActualPage(ByRef shp As Visio.Shape)
‘ ————————————————————————–
‘ 実ページ上のシェイプに対する業務ロジック
‘ 例: 座標のロギング、外部DB連携IDの付与など
‘ ————————————————————————–
Debug.Print “[Page Context] Processing Shape Name: ” & shp.Name & ” (ID: ” & shp.ID & “)”

‘ 例としてシェイプのシェイプシート(User.IsProcessed)を書き換える
If shp.CellExists(“User.IsProcessed”, visExistsLocally) = False Then
shp.AddNamedRow visSectionUser, “IsProcessed”, 0
End If
shp.Cells(“User.IsProcessed.Value”).FormulaU = “1”
End Sub

Private Sub ProcessInsideMaster(ByRef shp As Visio.Shape)
‘ ————————————————————————–
‘ マスターシェイプ内のシェイプに対する安全策
‘ マスターのマスターたる所以(テンプレートとしての定義)を保護する
‘ ————————————————————————–
Debug.Print “[Master Context] Skipping heavy business logic for Master Shape: ” & shp.Name

‘ ここでは実ページ用の特殊なデータバインドや座標計算は行わず、
‘ マスター独自の調整が必要な場合のみコードを記述する。
End Sub

4. プロジェクトリーダーからの実践的アドバイス

1. `ScreenUpdating` と `Undo` の制御を忘れるな
大量のシェイプを走査する際、描画更新が走るとパフォーマンスは劇的に低下する。さらに、意図しないマスターの変更がアンドゥバッファを圧迫することもある。処理の開始前には必ず `ScreenUpdating = False` とし、イミディエイトウィンドウやログで処理の安全性を確認しながら開発を進めてほしい。
2. データベースや外部ファイル連携時の罠
「図面上のシェイプID」をキーにして外部データベース(SQL ServerやSQLite、Excelなど)と同期を取るシステムを構築する場合、マスター内のシェイプがこのDB同期処理に巻き込まれると、マスター側のIDが実マスタの整合性を破壊する
DB連携処理の入口に、必ず今回紹介した `If Not shp.ContainingPage Is Nothing Then` のガードを挟むこと。これを徹底するだけで、現場からの「マクロを使うとステンシルがおかしくなる」というクレームを100%根絶できる。

結びにかえて

Visio VBAは、オブジェクトモデルの「階層構造」と「コンテキスト」を正確にハックすれば、これほど強力でオートメーション化の恩恵が大きいツールはない。甘い設計でコードを書き散らすのではなく、常に「今、このシェイプはどこに息をしているのか?」を意識し、エレガントで堅牢なシステムを構築してほしい。

君たちのコードが、現場の業務効率を何倍にも跳ね上げることを期待している。

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