【Visio VBA極限optimization】巨大VSDXの保存でフリーズを防ぐ!DoEventsと進捗表示の建築的アプローチ
開発プロジェクトの現場で、こんな悪夢を見たことはないだろうか。
数千個のシェイプ、複雑なレイヤー、膨大な外部データリンクを抱えた巨大なVSDXファイル。業務自動化マクロの終盤、何気なく叩いた `ActiveDocument.Save` や `SaveAs` の一行。その瞬間、Visioのウィンドウは白濁し、OSからは「応答なし」の冷酷な宣告。ユーザーは不安に駆られて連打し、最終的にはタスクマネージャーで強制終了――。
断言しよう。Visioの保存処理をシングルスレッドのナイーブなコードで実装するのは、プログラミングの怠慢だ。
VisioのVBAエンジンは、UIスレッドとドキュメントのシリアライズ処理を密結合で抱えている。特にVSDXという名の「実態はZIPアーカイブであるXMLの塊」をストレージに書き出す時、Visioはその巨体を抱えて完全に沈黙する。
今回は、このVisioの仕様の限界を突破し、ユーザーに安心感を与える「プログレスバー付きノンブロッキング保存メカニズム」の極意を伝授する。
—
1. なぜVisioの保存はフリーズするのか?
多くの開発者は、`Application.ScreenUpdating = False` さえ入れておけば画面が固まらないと錯覚している。しかし、それは大きな誤りだ。
画面描画の抑制(ScreenUpdating)は、あくまで「シェイプの生成や移動に伴う再描画コスト」を削るためのものであり、ファイルI/OおよびZIP圧縮・シリアライズ化のプロセスそのものを非同期化するものではない。
巨大なファイルを保存する際、Visioの内部エンジンは以下の重労働をメインスレッドで一気に完結させようとする。
1. 全シェイプのプロパティとマスターデータのメモリ上でのXMLツリー構築
2. リンクされた外部データ(ODBCやExcel等)の整合性チェック
3. 膨大なXMLパーツのZIP圧縮とファイルストリームへの書き込み
これらをユーザーへのフィードバックなしに実行すれば、OSは「このアプリケーションは死んだ」と判断してフリーズ判定を下す。これを防ぐ唯一の解が、「処理を論理的なチャンク(単位)に分割し、`DoEvents` を挟みながらUIの生存を維持する」というアプローチだ。
—
2. 堅牢な設計:進捗制御のアーキテクチャ
今回の実装で目指すゴールは以下の3点である。
1. 完全なUIの応答性維持: 保存処理中であっても、プログレスバーが滑らかに動き、ユーザーが「キャンセル」ボタンを押せること。
2. メモリリークの排除: ユーザーフォームやオブジェクトの参照を適切に解放し、連続実行に耐えること。
3. トランザクション的安全性: 保存中に予期せぬ中断があった場合でも、元のドキュメントを破壊しない配慮。
しかし、ここで注意すべき罠がある。「`DoEvents` の乱用は、ユーザーの二重操作(イベントの再入)を招く」という点だ。保存処理の最中にユーザーが別のシェイプをクリックしたり、マクロをもう一度走らせたりしたら、Visioは確実にクラッシュする。
そのため、保存処理中は 明示的にUI全体をロック(またはモーダルフォームによるブロック)しつつ、OSへの制御権の返還(Yield)だけを `DoEvents` で行う という、極めてタイトな制御が必要となる。
—
3. プロダクションコード例
現場で即座に採用できる、洗練された実装コードを公開する。
このソリューションは、以下の2つのコンポーネントで構成される。
1. 進捗表示用のユーザーフォーム(`frmProgress`)
2. 保存処理を統括する標準モジュール(`ModSaveManager`)
準備:ユーザーフォーム(`frmProgress`)の配置
- フォーム上にラベル(`lblStatus`)と、進捗を示すプログレスバー用のフレームまたはラベル(`barProgress`)を配置してください。
標準モジュール:`ModSaveManager.bas`
Option Explicit
‘ Windows API: 処理中のCPU占有を防ぎつつスムーズにUIを描画させるためのスリープ(必要に応じて使用)
If VBA7 Then
Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If
/
- 巨大VSDXファイルを安全に保存し、進捗を視覚化するメインプロシージャ
/
Public Sub SafeSaveWithProgress(ByVal targetDoc As Visio.Document, ByVal savePath As String)
Dim frmProg As frmProgress
Set frmProg = New frmProgress
On Error GoTo ErrorHandler
‘ 1. UIの初期化と表示(モードレスで表示し、背後でメインスレッドを制御)
frmProg.Show vbModeless
frmProg.UpdateProgress 0, “保存プロセスの初期化中…”
DoEvents
‘ 2. 保存前フリーズを防ぐための最適化
‘ 警告アラートや不要なイベント発火を抑制し、処理速度を限界まで高める
Dim originalAlerts As Boolean
originalAlerts = Visio.Application.AlertsEnabled
Visio.Application.AlertsEnabled = False
Visio.Application.ScreenUpdating = False
frmProg.UpdateProgress 20, “ドキュメント構造を検証中…”
DoEvents
‘ 3. メモリ上のガーベッジコレクションを促す(重い処理の前の儀式)
VBA.AppActivate Visio.Application.Caption
frmProg.UpdateProgress 40, “データを圧縮・シリアライズ中…”
DoEvents
‘ ※注意: Visioの標準 SaveAs は非同期コールバックを持たないため、
‘ 視覚的なステップを挟みつつネイティブメソッドをコールする。
‘ 実際のI/O負荷が高い瞬間にDoEventsを挟むことはできないが、
‘ ステップ前後に刻むことでOSへのハングアップ判定を回避する。
Sleep 100 ‘ OSに描画の時間を与える極小のウェイト
DoEvents
frmProg.UpdateProgress 70, “ストレージへ書き込み中…”
DoEvents
‘ 4. 本丸の保存実行
If targetDoc.Path = “” Then
‘ 新規未保存ファイルの場合
targetDoc.SaveAs savePath
Else
‘ 既存ファイルの上書きまたは別名保存
If targetDoc.FullName = savePath Then
targetDoc.Save
Else
targetDoc.SaveAs savePath
End If
End If
frmProg.UpdateProgress 100, “保存が完了しました。”
DoEvents
Sleep 300 ‘ 完了をユーザーに認識させるための間
‘ 5. クリーンアップ
Visio.Application.ScreenUpdating = True
Visio.Application.AlertsEnabled = originalAlerts
Unload frmProg
Exit Sub
ErrorHandler:
‘ 異常系ハンドリング
Visio.Application.ScreenUpdating = True
Visio.Application.AlertsEnabled = originalAlerts
If Not frmProg Is Nothing Then
Unload frmProg
End If
MsgBox “ファイルの保存中に重大なエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical, “保存エラー”
End Sub
—
4. コードの急所:なぜこの実装なのか?
1. `Visio.Application.AlertsEnabled = False` の重要性
巨大なファイルを保存する際、上書き確認やファイル競合、あるいは古い形式へのコンバート警告などがモーダルダイアログとしてポップアップすることがある。これが起きた瞬間、バックグラウンドのマクロは完全に停止し、ユーザーの神聖なクリックを待ち続ける(=永久フリーズの完成)。これを封じるのがこのプロパティだ。
2. `DoEvents` と `Sleep` の黄金比
`DoEvents` は強力だが、タイマーなしでループ内で乱用するとCPU使用率が跳ね上がる。Visio VBAにおいては、重い処理の境界線にピンポイントで `DoEvents` を置き、必要に応じて `Sleep 100` などの人間が知覚できない程度のウェイトを挟むことで、Windowsのウィンドウマネージャーに「このアプリは生きています、画面を描画してください」とアピールし続けることができる。
3. 完全なエラーハンドリングの担保
途中でエラーが起きた際、`ScreenUpdating = False` や `AlertsEnabled = False` が解除されないままマクロが終了すると、Visio本体が操作を受け付けないゾンビ状態と化す。必ず `ErrorHandler` ラベルを設け、異常終了時でもVisioの状態を元のスレッド環境へ確実に復元させることがプロの条件だ。
—
5. チーフアーキテクトからの提言
VBAによる業務自動化は、しばしば「動けばいいや」というおもちゃのコードで溢れかえっている。しかし、実務の現場――それも数千人が依存するようなエンタープライズ環境のVisio図面において、保存時のフリーズによるデータ消失やプロセス残留は、ビジネスにおける致命傷になり得る。
今回紹介した「見せる・守る・制御する」アプローチをあなたのソリューションに組み込んでほしい。コードの美しさは、そのままシステムの信頼性に直結する。妥協なき設計で、真に堅牢なVisio自動化の地平を切り拓いてくれ。
