【Visio VBA】描画負荷を制する者は大規模図面処理を制す:`Application.DeferRecalc` の深淵
君たち、Visio VBA で何をしている? 大量データを元に図形を自動生成したり、既存図形をバッチ処理したり。素晴らしい。業務効率化の最前線にいる諸君の熱意は、この私が肌で感じている。
だが、その処理、本当に「最速」と言えるか? 数千、数万にも及ぶ図形を相手にする時、Visio が画面描画のために行う「再計算」という処理が、どれほどの重荷になっているか、正確に理解しているか?
「処理中に画面が固まる」「実行に数分、いや数十分かかる」――。そんな悲鳴が聞こえてくるのは、まさにこの再計算の連鎖に起因する。図形一つ操作するたびに、Visio は「あ、何か変わったな。全体をもう一度見直そう」と、画面全体、あるいは関連する部分の描画情報を更新しようとする。これを、何千回、何万回と繰り返す。馬鹿げていると思わないか?
この連載では、Visio VBA の深淵を覗き込み、君たちが日夜格闘する大規模図面処理のボトルネックを解消し、プロダクションレベルの堅牢な自動化ツールを開発するための「極限の知見」を伝授する。
今回は、その第一歩として、`Application.DeferRecalc` という、まるで秘術のようなプロパティに焦点を当てる。これを使えば、Visio の再計算処理を一時的に「保留」し、一連の大量処理が終わった後にまとめて実行させることができる。これだけで、君たちのVBA処理は、まるで別次元の速さを手に入れるだろう。
なぜ `Application.DeferRecalc` が必要なのか? 再計算の連鎖という悪夢
Visio のオブジェクトモデルにおける `Application` オブジェクトは、Visio アプリケーションそのものを指し示す。そして、`Application.DeferRecalc` プロパティは、このアプリケーションの再計算動作を制御する。
デフォルトでは、`Application.DeferRecalc` は `False` である。これは、図形やページ、ドキュメントに対して何らかの変更を加えるたびに、Visio は即座に再計算を実行することを意味する。
君たちのコードが、例えば以下のような処理を繰り返していると想像してほしい。
‘ 1000個の図形を生成・配置するループ
For i = 1 To 1000
‘ 図形を作成
Dim shp As Visio.Shape
Set shp = ActivePage.Shapes.AddShape(“Rectangle”, 10, 10, 20, 20)
‘ 図形の位置を変更
shp.Cells(“PinX”).FormulaU = “100 ” & i
shp.Cells(“PinY”).FormulaU = “50 ” & i
‘ 図形の色を変更
shp.Cells(“FillForegnd”).FormulaU = “RGB(255, 0, 0)”
‘ … 他にも様々なプロパティ変更 …
Next i
このループの中で、`AddShape`、`Cells(“PinX”).FormulaU = …`、`Cells(“FillForegnd”).FormulaU = …` の各行が実行されるたびに、Visio は再計算を実行する。
- `AddShape`: 新しい図形が追加された。ページのレイアウト、他の図形との関連性などを再評価する必要がある。
- `Cells(“PinX”).FormulaU = …`: 図形の座標が変更された。他の図形との相対位置、スナップ、ガイド線など、多くの要素に影響を与える可能性がある。
- `Cells(“FillForegnd”).FormulaU = …`: 図形の色が変更された。表示上の描画情報を更新する必要がある。
これらが1000回繰り返される。Visio の内部では、描画キャッシュの更新、ジオメトリ計算、イベントの発火など、膨大な処理がバックグラウンドで走る。その結果、ユーザーからは「フリーズ」として観測されるのだ。
`Application.DeferRecalc = True`:魔法の呪文、あるいは聖杯
この悪夢を断ち切るための、まさに「魔法の呪文」こそが `Application.DeferRecalc = True` である。
このプロパティを `True` に設定すると、Visio は以降の図形やページに対する変更による再計算を一時的に停止する。まるで、作業中のキャンバスに「一時保護」のシートを被せるようなものだ。
そして、全ての変更処理が完了した後に、`Application.DeferRecalc = False` と設定することで、保留されていた再計算が一気に実行される。この時、Visio は「あっ、まとめてやればいいんだな」と、効率的な処理を実行してくれる。
これは、例えるなら、
- `False` (デフォルト): 料理人が一つ一つ具材を切っては火にかけ、炒めて、また次の具材を切っては火にかける…という非効率な作業。
- `True`: 必要な具材を全て切って、調味料を全て用意してから、一気に調理を開始する。
どちらが効率的かは、明白だろう。
プロダクションコード例:堅牢な設計と保守性を両立させる
では、この `DeferRecalc` をどのようにコードに組み込むべきか。単に `True` と `False` を置くだけでは不十分だ。予期せぬエラーが発生した場合、`DeferRecalc` が `False` に戻らず、Visio が描画停止状態のまま固まってしまう可能性がある。
プロダクションレベルのコードでは、エラーハンドリングと、処理の開始・終了を明確に分離することが極めて重要だ。
以下に、コピペしてそのまま使える、堅牢かつ保守性の高いVBAコード例を示す。
Option Explicit
‘—————————————————————————————
‘ Procedure : ProcessLargeNumberOfShapes
‘ Author : [Your Name/Company]
‘ Date : 2023/10/27
‘ Purpose : 大量の図形に対するバッチ処理を効率的に実行する
‘ Application.DeferRecalc を使用し、再計算負荷を軽減する
‘—————————————————————————————
Sub ProcessLargeNumberOfShapes()
Dim visApp As Visio.Application
Dim visDoc As Visio.Document
Dim visPage As Visio.Page
Dim shp As Visio.Shape
Dim startTime As Double
Dim endTime As Double
Dim i As Long
Const NUM_SHAPES_TO_CREATE As Long = 5000 ‘ 生成する図形の数 (例)
‘ — 初期設定 —
Set visApp = Visio.Application
Set visDoc = visApp.ActiveDocument
Set visPage = visApp.ActivePage
‘ — 処理開始 —
startTime = Timer ‘ 処理開始時刻を記録
‘ 描画再計算を一時保留する
‘ これにより、ループ中の図形操作による都度再計算を防ぎ、パフォーマンスを大幅に向上させる
visApp.DeferRecalc = True
‘ — エラーハンドリング設定 —
‘ エラー発生時も DeferRecalc が False に戻るように、On Error GoTo ErrorHandler を設定
On Error GoTo ErrorHandler
‘ — 大量図形処理ループ —
Debug.Print “大量図形処理を開始します。図形数: ” & NUM_SHAPES_TO_CREATE
For i = 1 To NUM_SHAPES_TO_CREATE
‘ 例: 四角形を一定間隔で配置し、色とサイズをランダムに設定
Dim xPos As Double, yPos As Double
Dim width As Double, height As Double
Dim fillColor As Long
xPos = (i Mod 100) 200 ‘ X座標を100個ごとに繰り返す
yPos = Int((i – 1) / 100) 200 ‘ Y座標を100個ごとにインクリメント
width = Rnd() 100 + 50 ‘ 50~150のランダムな幅
height = Rnd() 100 + 50 ‘ 50~150のランダムな高さ
fillColor = RGB(Rnd() 255, Rnd() 255, Rnd() 255) ‘ ランダムな色
‘ 図形を作成 (AddShape は再計算をトリガーしないが、後続のプロパティ設定はトリガーする)
Set shp = visPage.Shapes.AddShape(“Rectangle”, xPos, yPos, width, height)
‘ 図形のプロパティを設定
‘ これらの設定は DeferRecalc = True の間は即座に再計算されない
With shp
‘ FillColor プロパティは直接設定できないため、Cellsコレクション経由で設定
‘ FormulaU は Unicode 形式の数式を設定する
.Cells(“FillForegnd”).FormulaU = fillColor
‘ .Cells(“LineWeight”).FormulaU = “0.5 pt” ‘ 線幅も設定可能
‘ .Cells(“Style”).FormulaU = “0” ‘ 塗りつぶしなしにする場合など
End With
‘ 進捗表示 (Debug.Window に出力、数千個だと多すぎるので、適宜間引く)
If i Mod 100 = 0 Then
Debug.Print “処理中: ” & i & “/” & NUM_SHAPES_TO_CREATE
End If
Next i
Debug.Print “大量図形処理ループ完了。”
‘ — 処理終了、再計算を再開 —
‘ ここで DeferRecalc を False に戻すことで、保留されていた全ての再計算がまとめて実行される
visApp.DeferRecalc = False
‘ エラーハンドラをバイパス
On Error GoTo 0
endTime = Timer
MsgBox “大量図形処理が完了しました。” & vbCrLf & _
“処理時間: ” & Format(endTime – startTime, “0.00”) & ” 秒”, vbInformation
Exit Sub ‘ 正常終了時はエラーハンドラをスキップ
‘ — エラーハンドラ —
ErrorHandler:
‘ エラーが発生した場合でも、必ず DeferRecalc を False に戻す
‘ これにより、Visio が描画停止状態のまま固まるのを防ぐ
If Not visApp Is Nothing Then
If visApp.DeferRecalc = True Then
visApp.DeferRecalc = False
Debug.Print “エラー発生: DeferRecalc を False に戻しました。”
End If
End If
‘ エラーメッセージを表示
MsgBox “処理中にエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, vbCritical
‘ エラーをクリア
Err.Clear
End Sub
‘—————————————————————————————
‘ Procedure : ResetDeferRecalcOnWorkbookClose
‘ Author : [Your Name/Company]
‘ Date : 2023/10/27
‘ Purpose : Visio ファイルを閉じる際に、DeferRecalc が True のままになっていないか確認し、リセットする
‘ (ThisWorkbook.BeforeClose イベントなどで呼び出すことを想定)
‘—————————————————————————————
Sub ResetDeferRecalcOnWorkbookClose()
Dim visApp As Visio.Application
On Error Resume Next ‘ エラーが発生しても処理を続行
Set visApp = Visio.Application
If Not visApp Is Nothing Then
If visApp.DeferRecalc = True Then
visApp.DeferRecalc = False
Debug.Print “Visio ファイルクローズ時: DeferRecalc を False にリセットしました。”
End If
End If
On Error GoTo 0 ‘ エラーハンドリングを元に戻す
End Sub
コード解説と設計思想
1. `Option Explicit`: 変数宣言を強制し、typo によるバグを防ぎます。これは基本中の基本ですが、プロダクションコードでは必須です。
2. `visApp.DeferRecalc = True`: 処理の直前に設定します。
3. `On Error GoTo ErrorHandler`: 非常に重要です。もしループ中に予期せぬエラー(例えば、無効な座標値や、存在しないマスターシェイプを指定するなど)が発生した場合、VBA の実行は停止します。この `On Error` ステートメントがないと、`visApp.DeferRecalc` は `True` のままになり、Visio は描画停止状態から復帰できなくなります。
4. ループ処理: `AddShape`、`Cells(…).FormulaU = …` といった図形操作を大量に行います。`DeferRecalc = True` のおかげで、これらの操作はバックグラウンドで素早く実行され、画面描画の負荷がかかりません。
5. `visApp.DeferRecalc = False`: ループ処理が完了した後、そしてエラーが発生しなかった場合に、この行が実行されます。これにより、保留されていた再計算が一気に、かつ効率的に実行されます。
6. `On Error GoTo 0`: エラーハンドラを解除します。これ以降のエラーは、通常通り処理されます。
7. `Exit Sub`: 正常終了時に、エラーハンドラ部分をスキップするために設置します。
8. `ErrorHandler:` ラベル:
- `visApp.DeferRecalc = False`: 最優先事項です。エラーが発生した場合でも、必ず `DeferRecalc` を `False` に戻します。これにより、Visio が描画停止状態に陥るのを防ぎます。
- エラーメッセージの表示: 開発者やユーザーに、何が問題だったのかを伝えます。
9. `ResetDeferRecalcOnWorkbookClose` サブルーチン: これは、VBA マクロが置かれている Visio ファイル自体(`.vsdm` など)の `BeforeClose` イベントなどで呼び出すことを想定しています。万が一、VBA コードが中断されたり、ユーザーが予期せずファイルを閉じたりした場合でも、`DeferRecalc` が `True` のまま残るのを防ぐための「保険」です。
ファイル連携・データベース連携時の注意点
この `DeferRecalc` は、ファイルやデータベースからデータを読み込み、それに基づいて Visio 図形を生成・更新するシナリオで特に威力を発揮します。
- ファイル連携 (CSV, Excelなど):
- データを読み込む処理自体は、`DeferRecalc` の影響を受けません。
- 読み込んだデータを使って Visio 図形を操作する直前に `DeferRecalc = True` とし、全ての図形操作が終わった直後に `DeferRecalc = False` としてください。
- ファイル読み込みでエラーが発生した場合も、`DeferRecalc` が `False` に戻るように、エラーハンドリングを徹底してください。
- データベース連携 (SQL Server, Accessなど):
- データベースへのクエリ実行やレコードセットの取得は、`DeferRecalc` とは無関係です。
- 取得したデータを元に、Visio 図形を更新する部分で `DeferRecalc` を活用します。
- データベース接続エラーや、取得したデータに不正な値が含まれている場合なども考慮し、堅牢なエラーハンドリングを実装してください。
パフォーマンスチューニングの「次なる一手」
`DeferRecalc` は、大規模図面処理におけるパフォーマンスの「壁」を打ち破る強力な手段です。しかし、これはあくまで「描画負荷」という一面に焦点を当てた最適化に過ぎません。
もし、さらにパフォーマンスを追求したいのであれば、以下の点も考慮に入れるべきです。
- オブジェクトのライフサイクル管理: `Set obj = Nothing` を適切に使用し、不要になったオブジェクトをメモリから解放する。特に、ループ内で大量のオブジェクトを生成・破棄する際には重要です。
- `ScreenUpdating` の無効化 (Excel VBA の場合): Visio VBA には直接的な `ScreenUpdating` プロパティはありませんが、`DeferRecalc` がそれに近い役割を果たします。
- `EnableEvents` の無効化: イベント処理がパフォーマンスに影響を与える場合、一時的に `Application.EnableEvents = False` とすることで、イベント処理を無効化できます。ただし、これは非常に強力な設定なので、副作用を十分に理解した上で使用してください。
- アルゴリズムの再検討: そもそも、大量の図形を生成・操作するアルゴリズム自体に非効率な部分はないか? より少ないステップで目的を達成できる方法はないか?
- `Visio Professional` の利用: `Visio Professional` 版には、より高度な機能やパフォーマンス向上のためのAPIが搭載されている場合があります。
まとめ
Visio VBA における `Application.DeferRecalc` は、大規模図面処理のパフォーマンスを劇的に改善するための「必須スキル」です。画面描画の負荷を一時的に抑制することで、数千、数万もの図形を扱う際のフリーズを防ぎ、処理時間を大幅に短縮できます。
しかし、その恩恵を最大限に引き出すためには、単にプロパティをON/OFFするだけでなく、堅牢なエラーハンドリングと、処理の開始・終了を明確に分離する設計が不可欠です。今回示したプロダクションコード例を参考に、君たちの自動化ツールを、より信頼性が高く、高速なものへと昇華させてほしい。
次回の連載では、さらに踏み込んだオブジェクトモデルの活用法や、外部データ連携における高度なテクニックについて解説する予定だ。乞う、期待。
