【実務・中級編】【上級者向け】大規模なVSDXファイルを分割保存する際、各ページを個別の図面として切り出す高速処理 – Visio VBA解析バイブル

スポンサーリンク

【Visio VBA極限の知見】大規模VSDXファイルを秒速でページ分割・個別保存するアーキテクチャ

こんにちは。エンタープライズ領域の巨大なVisio図面(数万シェイプが鎮座するモンスターVSDX)を相手に日夜格闘している開発者の皆さん。

「全50ページある巨大なVSDXファイルから、特定のページだけを切り出して個別の図面ファイルとして保存したい」
そう要求されたとき、あなたはどう実装するだろうか?

まさか、マスターファイルを開き、不要なページを1枚ずつ地道に削除して上書き保存…なんて非効率なループを組んでいないだろうか。それをやれば、ガベージコレクションの嵐とメモリリークにより、VBAは必ずフリーズする。あるいは「ファイルがビジーです」という冷酷なエラーダイアログに阻まれることになる。

今回は、Visioのオブジェクトモデルの暗部を知り尽くしたチーフアーキテクトの私から、大規模VSDXファイルを爆速かつ堅牢にページ単位で切り出し・個別保存するための極限のテクニックを授けよう。

—

1. なぜ愚直な「ページ削除方式」は破綻するのか?

多くの初学者が陥るアンチパターンは以下の通りだ。

1. 元ファイルを開く
2. ループを回し、対象外のページを `Page.Delete` で消していく
3. 残った1ページを `Document.SaveAs` で保存する
4. ファイルを閉じる(これをページ数分繰り返す)

非効率な理由とVBAの罠

  • メモリの肥大化と断片化: Visioのシェイプやページを削除しても、内部のCOMオブジェクト参照が即座に解放されるとは限らない。削除処理のたびにVSDXの内部XML構造が再構築され、処理速度が幾何級数的に低下する。
  • ドキュメントの汚染: 削除を繰り返す中で参照切れや不正なリンクが生まれ、最悪の場合、ファイル自体が破損(Corrupt)する。

私たちが目指すべき「正解のアーキテクチャ」

「元ファイルを汚さず、必要なページだけを『新規ドキュメントに複製(Export/Copy)』して保存する」

このアプローチであれば、元ファイルは常に読み取り専用(Read-Only)扱いで安全、かつ無駄な削除処理を行わないため、驚異的な処理速度を叩き出すことができる。

—

2. 堅牢な分割保存を実現するための3つの極意

プロダクションコードを組むにあたり、以下の3点を徹底して実装に組み込む。

1. 画面描画の完全凍結(ScreenUpdating / DeferRecalc)
Visioはページが追加・変更されるたびに画面の再描画や数式の再計算を行おうとする。これを完全にシャットアウトすることで、処理速度が最大10倍以上変わる。
2. 完全修飾によるオブジェクトのスコープ管理
`ActivePage` や `ActiveDocument` などの暗黙的なグローバル参照を一切使わない。すべて変数に格納し、どのインスタンスを操作しているかを明確にする。
3. エラーハンドリングとCOM参照の確実な解放
巨大ファイルを扱う以上、メモリ不足やディスク容量不足のリスクは常にある。`On Error Goto` による安全な離脱経路を確保する。

—

3. 【コピペ即実戦投入可】ページ分割保存エンジンの実装コード

以下のコードは、指定したマスターVSDXの全ページを、1ページずつ独立したVSDXとして指定フォルダに高速出力するプロダクションコードだ。標準モジュールに貼り付けて実行してほしい。

Option Explicit

‘ ==============================================================================
‘ 処理名: ExportVisioPagesToIndividualFiles
‘ 概要 : 大規模VSDXの各ページを個別のVSDXファイルとして爆速で切り出し保存する
‘ ==============================================================================
Public Sub ExportVisioPagesToIndividualFiles()
Dim startTime As Double
startTime = Timer

‘ — 1. 設定フェーズ —
Dim sourceFilePath As String
Dim outputFolder As String

‘ ※実務に合わせてパスを変更してください
sourceFilePath = “C:\Data\MasterArchitecture.vsdx”
outputFolder = “C:\Data\OutputPages\”

‘ 出力先フォルダの存在確認・作成(簡易チェック)
If Dir(outputFolder, vbDirectory) = “” Then
MkDir outputFolder
End If

‘ — 2. Visio環境の最適化(パフォーマンス・爆速化のキモ) —
Dim originalAlerts As Boolean
originalAlerts = Application.AlertsEnabled
Application.AlertsEnabled = False
Application.ScreenUpdating = False
Application.DeferRecalc = True

Dim srcDoc As Document
Dim targetDoc As Document
Dim targetPage As Page
Dim srcPage As Page
Dim i As Long
Dim savedPath As String

On Error GoTo ErrorHandler

‘ — 3. マスターファイルを読み取り専用でオープン —
‘ Falseを渡すことで、元ファイルをロックせず安全に開く
Set srcDoc = Documents.OpenEx(sourceFilePath, visOpenRO + visOpenHidden)

Dim totalPages As Long
totalPages = srcDoc.Pages.Count
Debug.Print “【処理開始】総ページ数: ” & totalPages

‘ — 4. ページ単位の切り出しループ —
For i = 1 To totalPages
Set srcPage = srcDoc.Pages(i)

‘ 隠しページやバックグラウンドページをスキップする場合のガード
If srcPage.Background = False Then

‘ 新規ドキュメントを作成(マスターのステンシルや単位系を引き継ぐため、空のドキュメントではなくテンプレート指定か新規作成)
‘ ここではVisioのデフォルトメトリックで新規作成
Set targetDoc = Documents.Add(“”)

‘ ターゲット側のデフォルトページを取得、または新規追加
Set targetPage = targetDoc.Pages(1)

‘ ページ名を元ページと同じに設定(必要に応じて)
targetPage.Name = srcPage.Name

‘ ページのサイズ・プロパティを同期(必要最小限のプロパティコピー)
targetPage.PageSheet.CellsSRC(visSectionObject, visRowPageProps, visPageWidth).FormulaU = srcPage.PageSheet.CellsSRC(visSectionObject, visRowPageProps, visPageWidth).FormulaU
targetPage.PageSheet.CellsSRC(visSectionObject, visRowPageProps, visPageHeight).FormulaU = srcPage.PageSheet.CellsSRC(visSectionObject, visRowPageProps, visPageHeight).FormulaU

‘ 【最重要】シェイプを丸ごとコピー&ペースト(またはDropCopy)
‘ ページ内の全シェイプをターゲットページにドロップ・複製する
Dim shp As Shape
For Each shp in srcPage.Shapes
‘ Masterオブジェクトを持つシェイプの安全な複製
shp.Copy
targetPage.Paste
Next shp

‘ ファイル名の安全な生成(ファイル名に使えない文字の置換などは適宜実装してください)
savedPath = outputFolder & “Page_” & Format(i, “000”) & “_” & CleanFileName(srcPage.Name) & “.vsdx”

‘ 既存ファイルがある場合は上書き保存できるようにアラートを無効化しているのでそのままSaveAs
targetDoc.SaveAs savedPath
targetDoc.Close
Set targetDoc = Nothing

Debug.Print ” -> 完了: ” & savedPath
End If
Next i

‘ — 5. 正常終了処理 —
srcDoc.Close
Set srcDoc = Nothing

‘ 環境設定を復元
Application.DeferRecalc = False
Application.ScreenUpdating = True
Application.AlertsEnabled = originalAlerts

MsgBox “全ページの切り出しが完了しました!所要時間: ” & Format(Timer – startTime, “0.0”) & “秒”, vbInformation, “完了”
Exit Sub

ErrorHandler:
‘ — 6. 異常系ハンドリング —
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical, “致命的エラー”

‘ 確実なオブジェクト解放と環境復元
If Not targetDoc Is Nothing Then
On Error Resume Next
targetDoc.Close
Set targetDoc = Nothing
End If
If Not srcDoc Is Nothing Then
On Error Resume Next
srcDoc.Close
Set srcDoc = Nothing
End If

Application.DeferRecalc = False
Application.ScreenUpdating = True
Application.AlertsEnabled = originalAlerts
End Sub

‘ 簡易ファイル名クリーナー関数
Private Function CleanFileName(ByVal strName As String) As String
Dim invalidChars As Variant
Dim i As Long
invalidChars = Array(“\”, “/”, “:”, “”, “?”, “”””, “<", ">“, “|”)
For i = LBound(invalidChars) To UBound(invalidChars)
strName = Replace(strName, invalidChars(i), “_”)
Next i
CleanFileName = strName
End Function

—

4. コードの深掘り:なぜこの設計が「プロ仕様」なのか?

① `Documents.OpenEx` による読み取り専用・非表示オープン

通常の `Documents.Open` を使うと、Visioの画面上にファイルがチラつき、エディタの処理能力を著しく奪う。さらに、ファイルを排他ロックしてしまうため、他のプロセス(データベース連携やログ記録等)と競合を起こす原因になる。
`visOpenRO + visOpenHidden` を組み合わせることで、完全バックグラウンドでセキュアにメモリ上に読み込むことが可能となる。

② コピー&ペースト方式の採用と注意点

コード内では `shp.Copy` と `targetPage.Paste` を採用している。大規模な図面の場合、一括グループ化されたシェイプやマスターシェイプ(ステンシル)の依存関係が絡むため、単純な `Page.SaveAs`(Visioにはページ単体でのSaveAsメソッドは存在しないため、別名保存のテクニックが必要)よりも、シェイプ単位のクローン生成のほうがトラブルが少ない。
※もし図面内の接続(Connector)の整合性を完全に維持したい場合は、ドキュメント自体の複製後に不要ページを削除するアプローチ(※ただし例外処理を厳重にしたもの)を採るべきケースもあるが、描画速度と安定性を天秤にかけた場合、このシェイプ移譲方式が最も破綻しにくい。

—

5. データベースや外部システム連携時の実務アドバイス

このページ分割処理を、基幹システムやRDB(SQL Server / PostgreSQL等)からの夜間バッチ、あるいはRPAと連携させるプロジェクトを率いるリーダーへ、最後に実務的なアドバイスを送る。

  • 進捗の可視化:

数千ページに及ぶ巨大な設計書を処理する場合、標準の `MsgBox` ではユーザーがフリーズと勘違いする。ステータスバーへの出力 (`Application.StatusBar = “処理中… (” & i & “/” & totalPages & “)”`) を必ず挟むこと。

  • ログの非同期出力:

ファイル名や処理時間をCSVやDBにロギングする際、ファイルI/Oの頻度が高すぎるとボトルネックになる。ループの数が多い場合は、メモリ上に配列として蓄積し、最後に一括書き込みするアーキテクチャに昇華させると完璧だ。

理論と実務の境界線を理解し、リソースのライフサイクルをコントロールしたコードこそが、あなたの業務を真に自動化する。
現場で「動かない泥臭いコード」に悩むエンジニアがいたら、ぜひこの知見をシェアしてあげてほしい。

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