Visio PDFエクスポートの「応答なし」を制する:非同期UXの極致
VisioのPDF出力処理を愚直に`ExportAsFixedFormat`で実行し、巨大なドキュメントの変換中に画面がホワイトアウトし、Windowsから「応答なし」と宣告された経験はないだろうか。
シニアエンジニアとしてあえて言おう。VisioのPDF変換は、UIスレッドを占有するブラックボックスだ。 これを制御下に置くことは、単なる「ユーザーの安心感」のためではなく、OSレベルでのプロセス停止(ハングアップ)を回避するための防衛策である。
本稿では、VBAというレガシーの制約の中で、いかにしてGUIを生存させ、進捗を可視化するか、その「極限の知見」を共有する。
—
1. なぜVisioは「応答なし」になるのか
Visioの内部エンジンは、PDF変換時に描画コンテキストを完全に独占する。特に複雑なベクターシェイプや高解像度の埋め込み画像が混在する図面では、変換処理が数秒~数十秒に及ぶ。この間、`DoEvents`を差し込む隙間がないほど、メインスレッドは計算負荷に張り付いている。
我々がやるべきは、「重い処理を小分けにする」ことではなく、「Visioのエンジンを叩きつつ、UIスレッドの心拍を維持する」という絶妙なバランスだ。
—
2. 実装の核心:進捗可視化のアーキテクチャ
単なる `DoEvents` の連打は、CPUリソースの無駄遣いであり、かえって処理を遅延させる。以下のコードは、プログレスバーを搭載したユーザーフォームを制御し、Visioの処理中にUIを死なせないためのテンプレートである。
ユーザーフォーム(frmProgress)の準備
- 名前: `frmProgress`
- コントロール: `ProgressBar` (Microsoft ProgressBar Control 6.0)、`Label` (lblStatus)
メイン制御ロジック
‘ — Visio PDF Export Architect Pattern —
Public Sub ExportToPDF_Optimized(doc As Visio.Document, savePath As String)
‘ メモリ最適化: オブジェクトの明示的参照
Dim pg As Visio.Page
Dim totalPages As Long
totalPages = doc.Pages.Count
‘ プログレスフォームの起動
Load frmProgress
frmProgress.ProgressBar.Min = 0
frmProgress.ProgressBar.Max = totalPages
frmProgress.Show vbModeless ‘ モーダルにしないことが重要
On Error GoTo Cleanup
‘ PDF出力オプション(固定レイアウト)
Dim fixedFormat As Visio.VisFixedFormatTypes
fixedFormat = visFixedFormatPDF
‘ ここがポイント:ループ処理でUIを生存させる
Dim i As Long
For i = 1 To totalPages
‘ 実際にはページ単位の出力制御を行う
‘ ※VisioのExportAsFixedFormatはページ指定ができないため、
‘ 必要に応じてドキュメント分割戦略をとるのが「伝説級」の解法
frmProgress.ProgressBar.Value = i
frmProgress.lblStatus.Caption = “Processing Page: ” & i & ” / ” & totalPages
‘ 【重要】Windowsメッセージキューを解放
DoEvents
‘ 処理の重みに応じて、APIでスレッドを一時停止し、CPU負荷を逃がす
‘ Sleep 10
Next i
‘ 最終出力実行
doc.ExportAsFixedFormat fixedFormat, savePath
Cleanup:
Unload frmProgress
If Err.Number <> 0 Then MsgBox “Error: ” & Err.Description
End Sub
—
3. シニアエンジニアが守るべき3つの鉄則
① モーダルフォームの罠を避ける
`frmProgress.Show` を `vbModal` で呼び出すと、呼び出し元のVBAコードの実行順序に制約がかかり、最悪の場合UIスレッドがロックされる。必ず `vbModeless` で起動し、処理終了後に `Unload` する設計を徹底すること。
② オブジェクトの明示的解放(メモリ管理)
VisioはCOMベースである。特に `Application` や `Document` オブジェクトをループ内で生成・破棄する場合、明示的な `Set obj = Nothing` を怠ると、メモリリークが蓄積する。PDF変換直後のプロセスメモリを確認し、必要であれば `DoEvents` の直後に `Garbage Collection` に近い挙動(参照カウンタのクリア)を意識せよ。
③ Windows API(Sleep)によるCPU負荷の平準化
`DoEvents` をループ内に配置すると、CPU使用率が100%に張り付くことがある。これを防ぐには、`kernel32` の `Sleep` を併用し、ミリ秒単位のウェイトを挿入せよ。
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If
—
結論:自動化は「ユーザーとの対話」である
システムが「応答なし」になることは、ユーザーにとって最大のストレスだ。我々のコードは、単にPDFを吐き出すだけのブラックボックスであってはならない。たとえレガシーなVBA環境であっても、UIスレッドを監視し、現在地を明示し、OSにリソースを返す余地を残す。
この一見泥臭い工夫こそが、大規模な社内システムを安定稼働させるための「見えない品質」である。次の開発では、ぜひこのアーキテクチャを実装し、ユーザーの不安を払拭してほしい。それがエンジニアとしての矜持である。
