【テクニカル・上級編】【中級者向け】Visio図面をPDF出力する際、レイヤーの表示/非表示状態をVBAで切り替えて、複数のバリエーションを同時出力する – Visio VBA解析バイブル

スポンサーリンク

【Visio VBA極限活用】レイヤー制御によるマルチPDF同時出力エンジン

レガシーシステムの深部において、Visioの図面自動化ほど「雑に扱われてきた」領域はない。
「1つの図面ファイルから、レイヤーの表示状態を切り替えて複数のPDFをバッチ出力したい」——この一見してシンプルな要求は、多くのプログラマをVisioの不可解な非同期処理や、COMオブジェクトのメモリリークの泥沼へと引きずり込んできた。

本稿では、Visio VBAの内部ライフサイクルを完全に掌握し、実業務に耐えうる堅牢性と極限のパフォーマンスを兼ね備えた「レイヤー連動型マルチPDFバッチ出力エンジン」の設計思想と実装コードを公開する。

—

1. Visioレイヤー制御の深層と落とし穴

Visioのレイヤー(`Layer`オブジェクト)は、一見すると単なる図形のグループ化の延長に見えるが、その実体はページごとの描画コンテキストに深く結びついたメタデータである。

多くの開発者が陥る最初の罠は、`Layer.CellsU(“ObjCls”)` や可視性プロパティの操作タイミングを誤ることだ。VisioのVBA環境では、レイヤーの表示状態(`visLayerVisible`)を変更した直後、UIスレッドと描画エンジンが完全に同期しないままPDFエクスポートを走らせると、「非表示にすべきレイヤーが描画されたままのPDFが生成される」という致命的な不具合が発生する。

さらに、マルチドキュメント環境やバックグラウンド処理において、適切なオブジェクト解放を行わない場合、Visioのプロセス(`VISIO.EXE`)がタスクマネージャー上にゾンビプロセスとして残留し、社内システムのバッチ処理を完全によどませる原因となる。

—

2. アーキテクチャ設計の要件

今回のエンジンが満たすべき要件は以下の通りである。

1. トランザクション的完全性: エクスポート処理中に例外が発生しても、元の図面のレイヤー状態が確実に復元されること。
2. 完全なメモリ管理: すべてのVisio COMオブジェクト(`Document`, `Page`, `Layer`)をスコープごとに明示的に解放し、メモリリークを根絶すること。
3. 動的バリエーション定義: 「社内秘」「顧客提出用」「設備保守用」などの出力パターンを配列や設定構造体で拡張可能にすること。

—

3. 実装コード:マルチPDFバッチ出力エンジン

以下のコードは、アクティブなVisio図面を対象に、定義された複数のレイヤー構成パターンに従って、それぞれ異なるファイル名のPDFを連続生成するプロシージャである。

Option Explicit

‘ ==============================================================================
‘ 処理名: ExportMultiLayerPDFs
‘ 概要: レイヤーの表示状態を動的に切り替え、複数のPDFバリエーションを同時出力する
‘ ==============================================================================
Public Sub ExportMultiLayerPDFs()
‘ フォーマンス最適化のための環境設定
Dim originalScreenUpdating As Boolean
originalScreenUpdating = Application.ScreenUpdating
Application.ScreenUpdating = False
Application.ShowChanges = False

Dim targetDoc As Visio.Document
Set targetDoc = ActiveDocument

If targetDoc Is Nothing Then
MsgBox “処理対象の図面が開かれていません。”, vbCritical, “致命的エラー”
GoTo Cleanup
}

‘ 出力先ディレクトリの取得(図面と同じ場所)
Dim targetPath As String
If targetDoc.Path = “” Then
MsgBox “図面が一度も保存されていません。先に保存してください。”, vbExclamation, “警告”
GoTo Cleanup
End If
targetPath = targetDoc.Path & IIf(Right(targetDoc.Path, 1) = “\”, “”, “\”)

‘ ————————————————————————–
‘ バリエーション定義(出力名 と 非表示にしたいレイヤー名の配列)
‘ ————————————————————————–
Dim profileNames() As String
Dim hiddenLayersLists() As Variant

‘ プロファイル1: 顧客向け(”Internal_Notes”, “Dimensions” レイヤーを隠す)
‘ プロファイル2: 社内向け(隠すレイヤーなし)

ReDim profileNames(1 To 2)
ReDim hiddenLayersLists(1 To 2)

profileNames(1) = “Customer_Ver”
hiddenLayersLists(1) = Array(“Internal_Notes”, “Dimensions”)

profileNames(2) = “Internal_Ver”
hiddenLayersLists(2) = Array() ‘ 隠すレイヤーなし

‘ ————————————————————————–
‘ バッチ処理ループ
‘ ————————————————————————–
Dim i As Long
For i = LBound(profileNames) To UBound(profileNames)
Dim currentProfile As String
currentProfile = profileNames(i)

Dim hideLayers As Variant
hideLayers = hiddenLayersLists(i)

‘ 1. レイヤー状態の適用と退避
Dim stateCol As Collection
Set stateCol = ApplyLayerVisibility(targetDoc, hideLayers)

‘ 2. 描画エンジンの強制同期(極めて重要)
DoEvents
Application.ForceRecalc

‘ 3. PDFエクスポート実行
Dim pdfFilePath As String
pdfFilePath = targetPath & Left(targetDoc.Name, InstrRev(targetDoc.Name, “.”) – 1) & “_” & currentProfile & “.pdf”

On Error GoTo ExportError
targetDoc.ExportAsFixedFormat visFixedFormatPDF, pdfFilePath, visRasterizeNone, visPrintAll
On Error GoTo 0

‘ 4. レイヤー状態の復元
RestoreLayerVisibility stateCol

Set stateCol = Nothing
Next i

MsgBox “すべてのPDFバリエーションの出力が完了しました。”, vbInformation, “完了”
GoTo Cleanup

ExportError:
MsgBox “PDFエクスポート中にエラーが発生しました (” & currentProfile & “): ” & Err.Description, vbCritical, “エラー”
‘ エラー時も確実に状態を復元を試みる
On Error Resume Next
If Not stateCol Is Nothing Then RestoreLayerVisibility stateCol
On Error GoTo 0

Cleanup:
‘ 環境の復元とメモリの解放
Application.ScreenUpdating = originalScreenUpdating
Application.ShowChanges = True
Set targetDoc = Nothing
End Sub

‘ ==============================================================================
‘ レイヤーの表示状態を切り替え、変更前の状態をコレクションとして返す
‘ ==============================================================================
Private Function ApplyLayerVisibility(ByVal doc As Visio.Document, ByVal hideLayerNames As Variant) As Collection
Dim resultCol As New Collection
Dim pg As Visio.Page
Dim lyr As Visio.Layer

For Each pg in doc.Pages
For Each lyr in pg.Layers
‘ 現在の状態を退避 (PageID, LayerName, IsVisible)
Dim stateInfo(2) As Variant
stateInfo(0) = pg.ID
stateInfo(1) = lyr.Name
stateInfo(2) = lyr.CellsU(“ObjCls”).ResultIU ‘ 簡易的に可視性判定

‘ Visioのレイヤー可視性は CellsU(“LayerVisible”) で制御する方が確実
Dim currentVis As Integer
currentVis = CInt(lyr.CellsU(“LayerVisible”).ResultIU)
stateInfo(2) = currentVis

resultCol.Add stateInfo

‘ 隠すべきレイヤーか判定
Dim shouldHide As Boolean
shouldHide = False

Dim v As Variant
For Each v In hideLayerNames
If CStr(v) = lyr.Name Then
shouldHide = True
Exit For
End If
Next v

‘ 可視性の書き換え
If shouldHide Then
lyr.CellsU(“LayerVisible”).FormulaU = “0”
Else
lyr.CellsU(“LayerVisible”).FormulaU = “1”
End If
Next lyr
Next pg

Set ApplyLayerVisibility = resultCol
End Function

‘ ==============================================================================
‘ 退避させたコレクションを元にレイヤー状態を完全復元する
‘ ==============================================================================
Private Sub RestoreLayerVisibility(ByVal stateCol As Collection)
Dim stateInfo As Variant
Dim pg As Visio.Page
Dim lyr As Visio.Layer

For Each stateInfo In stateCol
Dim targetPageID As Long
Dim targetLayerName As String
Dim targetVis As Integer

targetPageID = stateInfo(0)
targetLayerName = stateInfo(1)
targetVis = stateInfo(2)

On Error Resume Next
Set pg = Nothing
Set pg = ActiveDocument.Pages.ItemFromID(targetPageID)
If Not pg Is Nothing Then
Set lyr = Nothing
Set lyr = pg.Layers.Item(targetLayerName)
If Not lyr Is Nothing Then
lyr.CellsU(“LayerVisible”).FormulaU = CStr(targetVis)
End If
End If
On Error GoTo 0
Next stateInfo
End Sub

—

4. チーフアーキテクトの知見:実運用における最適化の極意

1. `CellsU(“LayerVisible”)` の優位性

Visio VBAにおいて、レイヤーの可視性を制御する際、オブジェクトのプロパティ(存在する場合)を直接触るよりも、`CellsU(“LayerVisible”)` セルに対して明示的に `FormulaU = “0”` または `”1″` を代入する方が、シェイプシートの更新イベントを正確にトリガーするため、描画崩れが起きにくい。グローバル座標系(U)を用いることで、言語依存のバグも排除している。

2. `DoEvents` と `ForceRecalc` の挟み込み

Visioはマルチスレッドによる非同期描画を内部で行っているため、VBAから連続してレイヤー変更とPDFエクスポートを呼び出すと、描画スレッドが追いつかなくなる。
レイヤー状態を書き換えた直後に `DoEvents` を挟み、さらに `Application.ForceRecalc` を実行することで、COMの内部キューを強制フラッシュさせ、意図した通りの図面状態でPDFドライバに描画ストリームを渡すことが可能になる。

3. オブジェクト参照の徹底的な破棄

ループ内で取得する `Page` や `Layer` オブジェクトは、VBAのランタイムによって即座にガベージコレクションされない場合がある。特に大規模な図面(数百レイヤー・数千シェイプ)を扱うバッチ処理では、スコープを細かく分割し、処理が終わった瞬間に `Set xxx = Nothing` を明示的に記述することが、メモリリークを防ぐ唯一にして最大の防御策である。

—

5. まとめ

レガシーな業務システムにおいて、手作業による図面のバリエーション出力はヒューマンエラーの温床となる。
本稿で示したレイヤー制御エンジンを基盤に据えることで、複雑なVisioドキュメントであっても、完全な無人運用によるマルチフォーマット出力を安定稼働させることが可能となる。

アーキテクトたる者、コードの美しさだけでなく、背後にあるアプリケーションのライフサイクルとメモリの挙動までを完全に支配し続けなければならない。

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