【テクニカル・上級編】【実務直結】複数のVisioファイルから特定のページだけを抽出し、一つのマスターファイルに統合する「ページ集約ツール」 – Visio VBA解析バイブル

スポンサーリンク

Visio VBAを掌握する極限の知見:複数図面からの「ページ集約」自動化アーキテクチャ

Visio VBAによる開発において、最も忌むべきは「オブジェクトのゾンビ化によるメモリリーク」と「暗黙の参照解決による予期せぬ挙動」である。特に複数のVisioファイルをまたぐページ抽出・統合処理は、COMコンポーネントのライフサイクル管理の甘さが即座にExcelやVisio自体のフリーズ、あるいはメモリバグへと直結する地雷原だ。

本稿では、散在する複数図面から特定のページを抽出し、単一のマスターファイルへ完全無欠に統合する「ページ集約ツール」の全貌を、実務直結のVBAコードとともに解説する。

—

1. ページ集約におけるアーキテクチャの課題

複数ファイルを扱う自動化スクリプトで陥る罠は、単に `Documents.Open` と `Page.Copy` を繰り返すことにある。
Visioのドキュメントモデルは極めて重い。ページをコピーする際、内部でマスターシェイプやスタイルシート、さらには外部データリンクの参照までが解決されようとする。

シニアエンジニアが担保すべき設計要件は以下の3点である。

1. 完全なるオブジェクト解放: ループ内で生成される `Document` や `Page` インスタンスを確実に破棄し、COMの参照カウンタをゼロにする。
2. ページ名重複の動的解決: 異なるファイル間で同名のページが存在する場合の衝突回避(例外ハンドリング)。
3. バックグラウンド処理の徹底: 画面描画やアラートを完全に抑制し、実行速度を極限まで高める。

—

2. 実装コード:堅牢なページ集約エンジン

以下のコードは、指定フォルダ内の全Visioファイル(`.vsdx` / `.vsdm`)を走査し、特定の命名規則や条件に合致するページを、マスターファイルへ集約するプロダクション品質のVBAモジュールである。

Option Explicit

‘ ==============================================================================
‘ 複数ファイルからのページ集約メインプロシージャ
‘ ==============================================================================
Public Sub AggregateVisioPages()
Dim tStartTime As Double
tStartTime = Timer

‘ — 1. 環境の最適化 (パフォーマンス・安定性向上) —
With Application
.ScreenUpdating = False
.ShowAlerts = False
.Settings.ShowPageTabs = False ‘ 描画負荷の軽減
End With

On Error GoTo ErrorHandler

‘ — 2. パスおよびターゲット設定 —
Dim strSourceDir As String
strSourceDir = “C:\VisioData\SourceDrawings\” ‘ 統合元フォルダ

Dim docMaster As Visio.Document
Set docMaster = ActiveDocument ‘ 現在アクティブなファイルをマスターとする

Dim strFileName As String
strFileName = Dir(strSourceDir & “.vsd”)

Dim lngExtractedCount As Long
lngExtractedCount = 0

‘ — 3. ファイル走査ループ —
Do While strFileName <> “”
Dim strFullPath As String
strFullPath = strSourceDir & strFileName

‘ 自身を読み込もうとした場合はスキップ
If strFullPath <> docMaster.FullName Then
Dim docSource As Visio.Document
‘ 非表示かつ読み取り専用でオープン (メモリ消費とロックを回避)
Set docSource = Application.Documents.OpenEx(strFullPath, visOpenRO + visOpenHidden)

‘ — 4. ページ走査ループ —
Dim pgSource As Visio.Page
For Each pgSource In docSource.Pages
‘ 【条件分岐】特定のプレフィックスを持つページのみを抽出対象とする場合
‘ 例: ページ名が “System_” で始まるもの
If Left(pgSource.Name, 7) = “System_” Then

‘ ページをマスターに複製
pgSource.Junction 又は Copy の代替としてページオブジェクトを別ドキュメントへ移送
Dim pgNew As Visio.Page
Set pgNew = pgSource.Copy()

‘ コピーされたページをマスタードキュメントへ移動
pgNew.Document = docMaster

‘ 重複回避のためのページ名リネーム処理
Call SetupUniquePageName(docMaster, pgNew, pgSource.Name)

lngExtractedCount = lngExtractedCount + 1
End If
Next pgSource

‘ — 5. 抽出元ドキュメントの確実なクローズ —
docSource.Close
Set docSource = Nothing
End If

strFileName = Dir()
Loop

‘ — 6. 完了処理 —
Application.ScreenUpdating = True
Application.ShowAlerts = True
Application.Settings.ShowPageTabs = True

MsgBox “ページ集約が完了しました。” & vbCrLf & _
“統合されたページ数: ” & lngExtractedCount & vbCrLf & _
“処理時間: ” & Format(Timer – tStartTime, “0.00”) & ” 秒”, _
vbInformation, “完了”

Exit Sub

ErrorHandler:
‘ 異常系での環境復旧とオブジェクト解放
Application.ScreenUpdating = True
Application.ShowAlerts = True
Application.Settings.ShowPageTabs = True

If Not docSource Is Nothing Then
docSource.Close
Set docSource = Nothing
End If

MsgBox “致命的なエラーが発生しました。” & vbCrLf & _
“Error Number: ” & Err.Number & vbCrLf & _
“Description: ” & Err.Description, _
vbCritical, “エラー”
End Sub

‘ ==============================================================================
‘ ページ名の重複を動的に解決するサブルーチン
‘ ==============================================================================
Private Sub SetupUniquePageName(ByRef targetDoc As Visio.Document, ByRef targetPage As Visio.Page, ByVal baseName As String)
Dim strCandidateName As String
strCandidateName = baseName

Dim intIndex As Integer
intIndex = 1

Dim bExists As Boolean
Do
bExists = False
Dim p As Visio.Page
For Each p In targetDoc.Pages
If p.ID <> targetPage.ID And p.Name = strCandidateName Then
bExists = True
Exit For
End If
Next p

If bExists Then
strCandidateName = baseName & “_” & intIndex
intIndex = intIndex + 1
End If
Loop While bExists

targetPage.Name = strCandidateName
End Sub

—

3. チーフアーキテクトが解説するコードの急所

A. `Documents.OpenEx` による省リソースオープン

通常 `Documents.Open` を使うとVisioのGUI上に図面が次々と開き、描画処理のオーバーヘッドによって実行速度が劇的に低下する。さらに、ユーザーが誤って操作するリスクも生まれる。
ここでは `visOpenRO + visOpenHidden` フラグを使用し、「完全なヘッドレス(非表示)・リードオンリー」でメモリ上にのみドキュメントを展開している。これにより、巨大な図面を何十冊読み込んでもパフォーマンスが劣化しない。

B. ページオブジェクトのドキュメント間移送

Visio VBAにおける `Page.Copy()` は、コピー元ドキュメントのコンテキスト内で新しいページオブジェクトを生成する。その後、`pgNew.Document = docMaster` とプロパティを書き換えることで、安全にマスターファイル側へページの実体をアタッチすることが可能となる。

C. 厳格なエラーハンドリングとメモリ解放

COMオブジェクトのループ処理において、例外が発生した瞬間にドキュメントが開いたまま放置されると、ファイルがロックされ、次回の実行や手動操作に致命的な支障をきたす。
`ErrorHandler` ラベルを必ず設置し、予期せぬクラッシュ時であっても `docSource.Close` と `Set … = Nothing` が必ず実行される防衛的プログラミングを徹底している。

—

4. ささらなる高みへ:システム間連携と保守性の担保

このページ集約ツールを単なるローカルマクロで終わらせず、社内ニッチシステム(例えば、毎朝自動生成される設計書の夜間バッチなど)の一部として組み込む場合、以下の拡張が求められる。

  • Windows API(`Sleep` や `SetWindowPos`)の排除:

レガシーなVBAコードによく見られる「処理待ちのための `DoEvents` や `Sleep`」は、VisioのCOMモデルにおいては百害あって一利なしである。Visio自体の同期的完了を信頼し、無駄なウェイトを排除せよ。

  • ログ出力機構の付加:

`MsgBox` による対話型ではなく、処理結果(どのファイルからどのページが統合されたか)をCSVやテキストファイルに非同期で吐き出すロガーを組み込むことで、CI/CDパイプラインやWindowsタスクスケジューラでの完全無人運用が可能となる。

設計の美しさは、コードの行数ではなく「異常系への耐性とリソース管理の冷徹さ」に宿る。このアーキテクチャをベースに、あなたの現場の図面管理を極限まで自動化してほしい。

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