序文:再計算エンジンの暴走を制する者は、Visioを制す
現場の最前線で数千、数万のシェイプが蠢く大規模なエンジニアリング図面やネットワーク構成図をVBAで制御しようとした時、誰もが一度は「沈黙のフリーズ」に直面する。
1つのプロパティを書き換えるたびに、Visio内部のShapeSheetエンジンは、接続されたコネクタ、テキストの折り返し、ジオメトリの再配置を、愚直なまでに再計算し、UIを更新しようとする。数個の図形なら「親切な機能」だが、1,000個を超えればそれは「システムの毒」へと変わる。
私がチーフアーキテクトとして数々のレガシーシステムを再構築してきた中で、最も劇的な改善をもたらしたのは、アルゴリズムの刷新ではない。「Visioの思考を一時的に止める」という、極めて制御的なアプローチだ。
今回は、Visio VBAにおけるパフォーマンス最適化の核となる`Application.DeferRecalc`を中心に、Windows APIを用いた描画制御まで、その深淵を解説する。
—
1. ShapeSheetエンジンの連鎖反応を遮断せよ
Visioの本質は、描画ソフトではなく「関係性を持ったスプレッドシート(ShapeSheet)の集合体」である。
`Shape.Cells(“Width”).Formula = “2.0”` という一行が実行された瞬間、以下のプロセスが走る。
1. 当該セルの値の更新
2. 依存する他のセル(Height, PinX, etc.)の再計算
3. 接続されているコネクタの再配置計算
4. GDI+による描画バッファの更新
5. UIスレッドへの再描画命令
これを1,000回繰り返せば、1,000回の再描画コストが発生する。これを回避するのが `Application.DeferRecalc` だ。
DeferRecalcのメカニズム
`DeferRecalc = 1`(True)を設定すると、Visioは「ShapeSheetの再計算リクエストをキューに溜めるが、実行はしない」という状態に移行する。そして `DeferRecalc = 0`(False)に戻した瞬間、溜まったリクエストを一括で、決定論的に処理する。これにより、中間過程の無駄な計算をすべてスキップできるのだ。
—
2. 極限のパフォーマンスを実現する「三種の神器」
実戦投入レベルのコードでは、`DeferRecalc` 単体では不十分だ。以下の3つを組み合わせることで、初めて「極限」の速度域に到達する。
1. Application.DeferRecalc: 計算エンジンの停止
2. Application.ScreenUpdating: 描画更新の停止
3. Application.EventsEnabled: イベントハンドラの抑制(DocumentChanged等の連鎖防止)
これらを適切にカプセル化した、堅牢な実装例を以下に示す。
実践的最適化プロシージャ
‘—————————————————————————————
‘ Procedure : ExecuteMassiveUpdate
‘ Purpose : 大規模な図形更新を高速化するためのボイラープレート
‘ Note : エラーハンドリングを怠るとVisioのUIが固まったままになるため注意
‘—————————————————————————————
Public Sub ExecuteMassiveUpdate()
Dim vsoApp As Visio.Application
Set vsoApp = Visio.Application
‘ 状態のバックアップ(現在の設定を保持)
Dim oldScreenUpdating As Boolean: oldScreenUpdating = vsoApp.ScreenUpdating
Dim oldEventsEnabled As Boolean: oldEventsEnabled = vsoApp.EventsEnabled
Dim oldDeferRecalc As Integer: oldDeferRecalc = vsoApp.DeferRecalc
On Error GoTo ErrorHandler
‘ — 最適化開始 —
vsoApp.ScreenUpdating = False
vsoApp.EventsEnabled = False
vsoApp.DeferRecalc = 1 ‘ 再計算保留開始
‘ — 大規模処理のシミュレーション —
Dim vsoPage As Visio.Page
Set vsoPage = vsoApp.ActivePage
Dim i As Long
For i = 1 To 2000
‘ 大量描画などの重い処理をここに記述
‘ ここでは単純な矩形生成を例とする
vsoPage.DrawRectangle 0, 0, 1, 1
Next i
‘ ———————————-
CleanUp:
‘ — 最適化解除(逆順で戻すのが鉄則) —
‘ DeferRecalcを0に戻した瞬間、一括再計算が走る
vsoApp.DeferRecalc = oldDeferRecalc
vsoApp.EventsEnabled = oldEventsEnabled
vsoApp.ScreenUpdating = oldScreenUpdating
‘ オブジェクトの明示的解放(メモリリーク防止)
Set vsoPage = Nothing
Set vsoApp = Nothing
Exit Sub
ErrorHandler:
Debug.Print “Error: ” & Err.Description
Resume CleanUp
End Sub
—
3. Windows APIによる「究極の描画ロック」
VBAの標準機能だけでは、リボンメニューや特定のペインの再描画を完全に抑え込めないケースがある。特に、他システムからのCOMオートメーション経由でVisioを操作する場合、ウィンドウメッセージレベルでの制御が不可欠だ。
`User32.dll` の `SendMessage` を用い、Visioのメインウィンドウハンドルに対して `WM_SETREDRAW` を発行する。これは、Windows OSレベルで「このウィンドウの描画を一切禁ずる」という命令を下す、最も強力な手法だ。
APIを用いた高度な制御
‘ Windows APIの宣言
Private Declare PtrSafe Function SendMessage Lib “user32” Alias “SendMessageA” ( _
ByVal hWnd As LongPtr, _
ByVal wMsg As Long, _
ByVal wParam As LongPtr, _
ByRef lParam As Any) As LongPtr
Private Const WM_SETREDRAW As Long = &HB
‘—————————————————————————————
‘ 究極の高速化:OSレベルでの描画ロック
‘—————————————————————————————
Public Sub UltimateSpeedTest()
Dim vsoApp As Visio.Application
Set vsoApp = Visio.Application
‘ Visioのウィンドウハンドルを取得
Dim targetHWnd As LongPtr
targetHWnd = vsoApp.WindowHandle32
‘ OSレベルで描画を完全にロック
SendMessage targetHWnd, WM_SETREDRAW, 0, ByVal 0&
‘ 前述のDeferRecalc等も併用
vsoApp.DeferRecalc = 1
‘ — 巨大な処理 —
‘ (処理内容)
‘ 描画ロック解除と再計算
vsoApp.DeferRecalc = 0
SendMessage targetHWnd, WM_SETREDRAW, 1, ByVal 0&
‘ 最後に一度だけ強制描画
vsoApp.ActiveWindow.Activate
End Sub
—
4. プロフェッショナルが守るべき「鉄則」
大規模図面を扱う際、`DeferRecalc` を使うだけでは片手落ちだ。アーキテクトとして以下の点にも留意せよ。
オブジェクトのライフサイクル管理
VBAは参照カウント方式のメモリ管理を行っている。ループ内で `Shape` オブジェクトを生成し続ける場合、明示的に `Set shp = Nothing` を行わないと、内部的なキャッシュが膨れ上がり、`DeferRecalc` の効果を相殺するほどのメモリプレッシャー(およびスワップ)が発生する。
決定論的なエラー処理
`DeferRecalc = 1` に設定した後、ランタイムエラーでプロシージャが中断されると、Visioは「半死状態(操作しても何も反応しない)」に陥る。これはユーザーにとって致命的なバグだ。必ず `On Error GoTo` で確実に設定を復元するルートを確保せよ。
システム間連携のオーバーヘッド
Excelや外部DBからデータを流し込む場合、通信のオーバーヘッドを最小化せよ。1セルずつ書き込むのではなく、配列に展開してから一括でShapeSheetを操作する(`SetFormulas` や `SetResults` メソッドの活用)ことが、真のプロフェッショナルの仕事だ。
—
結言
`Application.DeferRecalc` は、単なる高速化オプションではない。それは、Visioという巨大なステートマシンの挙動を、開発者の支配下に置くための「安全装置」である。
レガシーなVBA環境であっても、Windows APIとオブジェクトモデルの深い理解を組み合わせれば、最新のフレームワークに劣らない圧倒的なパフォーマンスを引き出すことができる。道具を嘆く前に、その道具の真の力を引き出せているか。この問いこそが、我々エンジニアが常に自らに課すべき命題である。
