大規模プロジェクトにおける「画面更新停止」と「計算モード制御」によるVBA高速化の極意
Microsoft Project(以下、MSP)のVBA開発において、数万件規模のタスクを持つWBSや、複雑な依存関係(前提条件)が網羅されたスケジュールをプログラムで操作した経験はあるだろうか。
もし、数千行のタスク追加やリンク貼付を愚直にデフォルトの実行環境で行ったとすれば、処理が完了するまでにコーヒーを何杯も飲むことになる。最悪の場合、OSの応答が途絶え、COM例外による強制終了(Fatal Error)の憂き目に遭う。
なぜMSPのVBAはこれほどまでに遅いのか。
原因は明白だ。MSPの「計算エンジン(Calculation Engine)」と「UIの再描画(Redraw)」が、コードの1行実行ごとに走るからである。タスクAの開始日を変えるたびに、クリティカルパスの再計算、全後続タスクのフォワード/バックワードパスの走査、そしてガントチャートのレンダリングが裏で同期実行される。
本稿では、レガシーかつ強大なProject VBAの制約をねじ伏せ、数万タスクの更新を「ミリ秒単位」で完遂するための実行環境制御の極意を、シニアエンジニアの視点から授ける。
—
1. プロジェクト計算エンジンの完全掌握:`Calculation` の支配
多くの初学者は、Excel VBAのノリで `Application.ScreenUpdating = False` だけを設定して満足する。しかし、MSPにおいてはそれだけでは不十分だ。Excelにはない「プロジェクト計算モード(Calculation Mode)」の制御が勝負の分かれ目となる。
MSPの計算モードには、主に以下の3つのステートが存在する。
1. 自動計算 (`pjCalculationAutomatic`): デフォルト。データ変更の都度、全ネットワークのスケジュール計算が走る。
2. 手動計算 (`pjCalculationManual`): データ変更時の自動計算を停止する。
3. バックグラウンド計算: リソースやコストの最適化を裏で処理するモード。
大規模な一括処理を行う際は、必ず計算モードを手動に切り替え、処理の最後に明示的な再計算(リカルク)を強制する必要がある。
‘ 計算モードを退避し、手動に切り替える
Dim originalCalculation As Long
originalCalculation = Application.Calculation
Application.Calculation = pjCalculationManual
ここで注意すべきは、単に計算を止めるだけでは、タスク間の依存関係(Predecessors / Successors)をプログラムで構築する際に「計算されていないプレースホルダー値」を掴まされるリスクがある点だ。
そのため、一括構築フェーズの終了時には、以下のように強制再計算(CalculateAll)を挟む必要がある。
‘ 全プロジェクトの強制再計算
Application.CalculateAll
—
2. 画面描画の凍結とWindows APIレベルでの最適化
MSPのUIスレッドとCOMオブジェクトの通信は非常に重い。`Application.ScreenUpdating = False` はVBAのオブジェクトモデルにおける描画を止めるが、Windowsメッセージキューレベルでのブロッキングを防ぐためには、さらなるアプローチが有効だ。
極限のパフォーマンスを求める現場では、Excel同様に `Interactive` プロパティも併用し、ユーザーからのマウス・キーボード入力を完全に遮断する。これにより、OS割り込みによるコンテキストスイッチのオーバーヘッドを劇的に削減できる。
Sub FreezeEnvironment(ByRef savedScreenUpdate As Boolean, ByRef savedInteractive As Boolean, ByRef savedCalc As Long)
‘ 状態の退避
savedScreenUpdate = Application.ScreenUpdating
savedInteractive = Application.Interactive
savedCalc = Application.Calculation
‘ 実行環境の凍結
Application.ScreenUpdating = False
Application.Interactive = False
Application.Calculation = pjCalculationManual
End Sub
Sub UnfreezeEnvironment(ByVal savedScreenUpdate As Boolean, ByVal savedInteractive As Boolean, ByVal savedCalc As Long)
‘ 状態の復元(順序が極めて重要)
Application.Calculation = savedCalc
Application.Interactive = savedInteractive
Application.ScreenUpdating = savedScreenUpdate
‘ 最終的な整合性担保のための再計算
If savedCalc = pjCalculationAutomatic Then
Application.CalculateAll
End If
End Sub
—
3. 【実践】数万タスクを一瞬で構築する高効率トランザクション・パターン
それでは、上記の理論を統合した「大規模WBS生成・依存関係設定エンジン」の実装パターンを提示する。
オブジェクトの参照を極力減らし、ループ内でのCOM境界を跨ぐ通信を最小限に抑えるのが、真に洗練されたVBAコードである。
‘ ==============================================================================
‘ 模块名: Mdl_HighSpeedWBSBuilder
‘ 概要: 数万件規模のタスク生成と依存関係設定を極限まで高速化するトランザクション処理
‘ ==============================================================================
Option Explicit
Public Sub ExecuteHighSpeedWBSImport()
Dim tStart As Double
tStart = Timer
‘ 環境退避変数
Dim orgScreen As Boolean
Dim orgInteractive As Boolean
Dim orgCalc As Long
‘ 1. 実行環境の凍結(エラーフック必須)
On Error GoTo ErrorHandler
FreezeEnvironment orgScreen, orgInteractive, orgCalc
‘ ステータスバーへのメッセージ出力(UI非同期)
Application.StatusBar = “大規模WBSの構築を開始しています…”
‘ 2. アクティブプロジェクトの取得
Dim prj As Project
Set prj = ActiveProject
‘ 既存タスクのクリア(必要に応じて)
‘ Dim t As Task
‘ For Each t In prj.Tasks
‘ If Not t Is Nothing Then t.Delete
‘ Next t
‘ 3. 高速一括タスク追加シミュレーション(例:5000タスク)
Dim i As Long
Dim tsk As Task
‘ 画面描画も計算も止まっているため、このループは驚異的な速度で完了する
For i = 1 to 5000
Set tsk = prj.Tasks.Add(“タスク_” & Format(i, “00000”))
tsk.Duration = “8d” ‘ 8日間
‘ 階層構造(WBS)の構築例(3階層サマリー)
If i Mod 100 = 1 Then
tsk.OutlineLevel = 1
ElseIf i Mod 10 = 1 Then
tsk.OutlineLevel = 2
Else
tsk.OutlineLevel = 3
End If
Next i
‘ 4. 依存関係(前提条件)の高速バッチ構築
‘ 注意: 計算モードがManualのため、リンクを貼るだけでは日付は連動しない
Application.StatusBar = “依存関係(プレデセッサ)を構築中…”
For i = 2 to 5000
‘ 例: 簡易的に直前のタスクを前提条件(FS形式)に設定
‘ Task.TaskDependencies.Add のオーバーヘッドを避けるため簡易プロパティ操作
prj.Tasks(i).Predecessors = CStr(i – 1)
Next i
‘ 5. 全体整合性のための強制再計算とスケジュール適用
Application.StatusBar = “プロジェクト全体のスケジュールを再計算中…”
Application.CalculateAll
‘ 正常終了
UnfreezeEnvironment orgScreen, orgInteractive, orgCalc
Application.StatusBar = “”
MsgBox “高速WBS構築完了 処理時間: ” & Format(Timer – tStart, “0.00”) & ” 秒”, vbInformation
Exit Sub
ErrorHandler:
‘ 異常終了時も必ず環境を復元する(デッドロック防止)
UnfreezeEnvironment orgScreen, orgInteractive, orgCalc
Application.StatusBar = “”
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical
End Sub
—
4. シニアエンジニアが知るべき「メモリ管理」と「COM解放」の罠
VBAにおける最大の悪習は、`ActiveProject` や `ActiveSelection`、あるいはループ内で暗黙的に生成されるCOMラッパーオブジェクトを解放せず放置することだ。
数万回のループ内で `Set tsk = prj.Tasks.Add(…)` のようなコードを回すとき、VBAの背後にあるCOM RCW(Runtime Callable Wrapper)の参照カウントは肥大化し、ガベージコレクションが追いつかなくなる。これが原因でメモリリークが発生し、途中で「メモリ不足(Out of memory)」エラーを引き起こす。
確実なオブジェクト解放の鉄則
1. ループ内で生成する一時オブジェクトの変数は、極力使い回すか、ループのスコープを小さくする。
2. モジュールレベルの変数やグローバル変数にMSPのオブジェクトをむやみに保持させない。
3. 処理が終わったオブジェクト変数は、必ず `Set obj = Nothing` で明示的に解放する。
‘ 良い例:ループ内で変数を再利用し、最後に確実に解放する
Dim prj As Project
Set prj = ActiveProject
Dim tsk As Task
Dim i As Long
For i = 1 To 10000
Set tsk = prj.Tasks.Add(“Task ” & i)
‘ 処理…
Set tsk = Nothing ‘ 個別の参照を即座に捨てる
Next i
Set prj = Nothing
—
5. まとめ:レガシーの殻を破るシステムアーキテクチャへ
Project VBAは、一見すると古臭い自動化ツールに見えるかもしれない。しかし、その背後にあるMicrosoft Projectの計算エンジンは、エンタープライズの数万規模の複雑なリソース配分やクリティカルパスを正確に弾き出す極めて高度な代物である。
そのエンジンをねじ伏せ、意のままに高速制御するためには:
- `Application.Calculation = pjCalculationManual` による計算エンジンの完全停止
- `Application.ScreenUpdating` と `Application.Interactive` によるUI/OS割り込みの遮断
- COMラッパーのライフサイクルを意識した厳格なメモリ管理 (`Set Nothing`)
- 例外発生時(Error Handler)を考慮した堅牢な環境復元ロジック
これらを実装に組み込むことだ。この知見を体得した瞬間から、あなたの書くVBAコードは、単なる「マクロ」から、大規模プロジェクトを支配する「エンタープライズ・エンジン」へと昇華する。
