Visio VBAを掌握する極限の知見:開いている全てのドキュメントを「安全かつ確実に」一括保存・解放するアーキテクチャ
業務自動化エンジニアの私から、Visio VBA開発における初期の大きな罠、そしてそれを美しく回避するプロの設計手法を授けよう。
「複数開いているVisio図面を、ボタン一つで全部上書き保存して閉じたい」
——一見、初歩的な要件に思えるだろう。ネットを検索すれば `For Each doc In Documents` のようなサンプルコードがいくらでも転がっている。しかし、その甘いコードをそのまま本番環境に投入すれば、遠からず「保存されていない新規図面(未命名)」での予期せぬダイアログ停止や、メモリリーク・ゾンビプロセスの発生という悪夢に直面することになる。
今回は、実務の現場で「絶対に止まらない」堅牢性を誇る、Visioドキュメントの一括制御ルーチンの全貌を論理的に解説する。
—
1. なぜ「単純なループ」では実務で破綻するのか?
初心者が陥る最大の過ちは、`Documents` コレクションを上から順に舐めるだけのコードを書くことだ。実務の現場には、以下の「例外」が常に存在する。
1. 未保存の新規ドキュメント(Pathを持たない)
- `Save` メソッドをいきなり叩くと、ファイル名と保存先を尋ねるダイアログがポップアップし、バッチ処理がそこで完全停止する。
2. ステンシル(`.vssx`)や他アドインの裏画面
- ユーザーが意図しないドキュメント(Visioが内部制御するマスターステンシルなど)まで走査対象に含まれてしまい、書き込み権限エラーや予期せぬ挙動を引き起こす。
3. コレクションの動的操作によるインデックスズレ
- ループ内でドキュメントを閉じる(`Close`)処理を行うと、コレクションのインデックスが変動し、未処理のドキュメントがスキップされるか実行時エラー(Runtime Error)を引き起こす。
これらを完全にコントロール下に入れなければ、プロの業務自動化ツールとは言えない。
—
2. 堅牢な一括保存・解放アーキテクチャの設計思想
本コードで実装する3つの防衛策を頭に叩き込んでほしい。
- 逆順ループ(または安全なコレクション退避)の原則
ドキュメントをクローズしてコレクションから除外していく場合、インデックスの若い順(`1 To Count`)で回すと必ず破綻する。後ろから前へ(`Count To 1 Step -1`)処理するのが鉄則だ。
- パスの有無による保存戦略の分岐
`doc.Path` が空文字(まだ一度も保存されていない)の場合のフォールバック処理(自動命名保存、あるいはスキップ)を明示的に実装する。
- エラーハンドリングとメモリの完全解放
想定外のエラーが発生してもVisioインスタンスやファイルロックを残さないよう、トランザクション的な視点で記述する。
—
3. 【プロダクションコード】一括保存・クローズ・メモリ解放マクロ
以下のコードをVBAエディタ(標準モジュール)に貼り付ければ、そのまま実務の現場で稼働する。コピペの許容範囲を超えた、実戦仕様のコメント付きコードだ。
Option Explicit
‘ ==============================================================================
‘ 処理名 : SaveAndCloseAllDocuments
‘ 概要 : 開いているすべての実ドキュメントを安全に保存し、クローズする
‘ 備考 : ステンシルや未保存ファイルへの配慮を組み込んだプロダクションコード
‘ ==============================================================================
Public Sub SaveAndCloseAllDocuments()
Dim i As Long
Dim doc As Visio.Document
Dim targetCount As Long
‘ 処理開始のトーストやステータス表示(必要に応じて)
Visio.Application.ShowProgressWindow True
On Error GoTo ErrorHandler
‘ 1. Documentsコレクションの総数を取得
targetCount = Visio.Documents.Count
If targetCount = 0 Then
MsgBox “処理対象のドキュメントは開かれていません。”, vbInformation, “情報”
GoTo Finally
End If
‘ 2. コレクションの破壊を防ぐため、後ろから前へ(逆順)ループを回す
For i = targetCount To 1 Step -1
Set doc = Visio.Documents(i)
‘ 【重要】Visioの内部ステンシルやテンプレート、読み取り専用の特殊ドキュメントを除外
If Not doc.ReadOnly And doc.Type = visTypeDrawing Then
‘ すでに一度でも保存されている(Pathが存在する)場合のみ上書き保存
If doc.Path <> “” Then
If doc.Saved = False Then
doc.Save
Debug.Print “上書き保存完了: ” & doc.FullName
End If
Else
‘ 【設計方針】未保存ファイルへの対応
‘ ここでは自動でデスクトップにタイムスタンプ付きで保存する例とする
Dim defaultPath As String
defaultPath = CreateAutoSavePath(doc.Name)
‘ 新規ドキュメントとして名前をつけて保存
doc.SaveAs defaultPath
Debug.Print “新規自動保存完了: ” & defaultPath
End If
‘ 3. 保存が確実に行われたドキュメントを安全にクローズ
doc.Close
Else
Debug.Print “スキップ対象(ステンシルまたは読取専用): ” & doc.Name
End If
Next i
MsgBox “すべての図面の保存とクローズが正常に完了しました。”, vbInformation, “完了”
Finally:
‘ 4. プログレス表示の復元とメモリ・オブジェクトの解放
Visio.Application.ShowProgressWindow False
Exit Sub
ErrorHandler:
‘ 異常系ハンドリング:予期せぬエラー時にプロセスを路頭に迷わせない
MsgBox “エラーが発生しました。” & vbCrLf & _
“Error No: ” & Err.Number & vbCrLf & _
“Description: ” & Err.Description, vbCritical, “致命的なエラー”
Resume Finally
End Sub
‘ ==============================================================================
‘ 補助関数 : 未保存ファイルに対する一意のパス生成
‘ ==============================================================================
Private Function CreateAutoSavePath(ByVal originalName As String) As String
Dim wsh As Object
Dim desktopPath As String
Dim timeStamp As String
Set wsh = CreateObject(“WScript.Shell”)
desktopPath = wsh.SpecialFolders(“Desktop”)
‘ 衝突を防ぐため、タイムスタンプを付与
timeStamp = Format(Now, “yyyymmdd_hhnnss”)
‘ 拡張子がなければ補正、あればそのまま利用
CreateAutoSavePath = desktopPath & “\AutoSaved_” & timeStamp & “_” & originalName
Set wsh = Nothing
End Function
—
4. エンジニアとしてのこだわり:なぜこの実装なのか
逆順ループ(`Step -1`)の優位性
もし `For i = 1 To targetCount` でループを回し、途中で `doc.Close` を実行すると、コレクションのインデックスが繰り上がるため、「1つ飛ばしで処理されるファイル」や「Subscript out of range(インデックスが有効範囲にありません)エラー」が確実発生する。後ろから処理することで、配列やコレクションの再インデックス化の影響を完全に遮断できる。
`visTypeDrawing` によるフィルタリング
Visioは、ユーザーが意識しないところで様々な内部ドキュメントやアドイン用のステンシルをメモリ上にロードしている。これらを `Save` しようとするとエラーになる。`doc.Type = visTypeDrawing` を条件に組み込むことで、「人間が描画している通常の図面ファイル(`.vsd`, `.vsdx`)」のみを安全にターゲットにできるのだ。
—
5. まとめ:ツール開発は「守り」の堅牢性から
業務自動化スクリプトの価値は、「いかに速く動くか」ではなく、「いかなる例外的な状況(未保存、ファイル競合、読取専用など)が起きても、ユーザーのデータを絶対に破壊せず、綺麗に終了できるか」という「守りの堅牢性」に宿る。
今回のコードをベースに貴方のプロジェクトへ組み込めば、ファイル管理に起因するトラブルはゼロになるはずだ。ぜひ、現場の生産性向上に役立ててほしい。
