【上級プロ】Projectの「計算エンジン」をVBAで一時停止・再開する際の一括更新テクニック
こんにちは。開発プロジェクトの現場で数々の魔改造されたVBAコードと向き合ってきたチーフアーキテクトだ。
Excel VBAの感覚でMS Project(Microsoft Project)を触り、痛い目を見たことはないだろうか?
「何千行ものタスクを一括追加・更新したら、ガントチャートがガタガタになった」「処理の途中で予定日が勝手に繰り上がり、想定外のスケジュールが算出された」。
これらはすべて、MS Projectの「計算エンジン」が持つオブジェクトのライフサイクルと、同期のタイミングを誤っていることが原因だ。
今回は、Project VBAにおける計算エンジンの制御(`Application.Calculation`)の本質と、依存関係の不整合を完全に防ぎながら高速化を実現する「堅牢な一括更新パターン」を叩き込む。
—
なぜExcelの常識が通用しないのか? Project計算エンジンの闇
Excelの場合、`Application.Calculation = xlCalculationManual` とすれば、数式は完全に沈黙し、意図したタイミングで `Calculate` を叩くまで勝手にセルが書き換わることはない。
しかし、MS Projectは違う。
Projectのタスク(`Task`)は、WBSの階層構造、先行・後続タスクの依存関係(リンク)、カレンダー、リソース割り当てなど、数式ではなく「動的なスケジュール制約エンジン」によって常に支配されている。
計算エンジンを停止(`pjManual`)させても、オブジェクトモデルを操作した瞬間にメモリ上ではスケジュール再計算のキューが蓄積される。そして、安易なタイミングで計算を再開させたり、プロパティを中途半端に読み書きしたりすると、以下の惨劇が起きる。
1. 依存関係の逆転: 先行タスクの終了日より前に後続タスクの開始日が強制的に押し戻され、クリティカルパスが破損する。
2. 無駄な再計算ループ: ループの1回ごとにスケジュールの再帰計算が走り、数千件のタスク操作に数十分を要する(O(N^2)地獄)。
3. 強制的な制約の付与: 勝手に「できるだけ早く(ASAP)」以外の制約が入る。
これを防ぐには、「計算の停止 ➔ トランザクション的な一括データ流し込み ➔ 依存関係の整合性担保 ➔ 計算の再開と強制フラッシュ」 という厳密なライフサイクル管理が必要だ。
—
堅牢な一括更新アーキテクチャの設計方針
プロダクション環境で耐えうるコードを書くための鉄則は以下の3点だ。
1. ScreenUpdating と Calculation の二重封鎖
画面描画と計算エンジンの両方を止めなければ意味がない。
2. エラーハンドリング(`On Error Goto`)の絶対死守
処理途中でバグや例外が発生した際、計算停止状態のままVBAが終了すると、ユーザーのProjectファイル全体が破壊される。必ず `Finally` ブロック(エラー時も通る復元処理)を実装すること。
3. リンク(TaskDependency)と基本プロパティの分離
タスクの基本情報(名前・期間)を作成してから、依存関係を一括で結ぶ。この順序を守るだけでバグの9割は消滅する。
—
【実戦用】コピペで使えるプロダクションコード
以下のコードは、外部データベースやCSVから取得したタスク群を、計算エンジンを停止して安全かつ爆速でProjectに流し込むためのテンプレートだ。
Option Explicit
Sub Enterprise_BatchTaskUpdate()
‘ =========================================================================
‘ 処理名: 外部データ一括同期エンジン(計算エンジン制御版)
‘ 概要 : 計算と画面描画を停止し、安全にタスク群を一括生成・更新する
‘ =========================================================================
Dim startTime As Double
startTime = Timer
‘ 1. エラー時の復元用フラグメント
Dim originalCalculation As Long
Dim originalScreenUpdating As Boolean
On Error GoTo ErrorHandler
‘ 2. 【最重要】環境の退避と計算エンジンの完全停止
originalCalculation = Application.Calculation
originalScreenUpdating = Application.ScreenUpdating
Application.ScreenUpdating = False
Application.Calculation = pjManual ‘ 計算を手動(停止)に設定
‘ 処理中の警告ダイアログ等を抑制して爆速化
izerApp.DisplayAlerts = False
‘ — 【トランザクション開始】 —
Debug.Print “[INFO] トランザクション開始: 計算エンジン停止”
Dim activeProj As Project
Set activeProj = ActiveProject
‘ — サンプル:既存タスクのクリア(必要に応じて) —
‘ Dim t As Task
‘ For Each t In activeProj.Tasks
‘ If Not t Is Nothing Then t.Delete
‘ Next t
‘ — モックデータ流し込み(実際にはDBやExcelからループで取得する想定) —
Dim i As Long
Dim newTask As Task
For i = 1 to 500
‘ タスクの追加(計算が止まっているので瞬時に終わる)
Set newTask = activeProj.Tasks.Add(“タスク_” & i)
newTask.Duration = “3d” ‘ 3日間
‘ ※注意: ここで先行タスク等のリンクを張らない!
‘ 依存関係はすべてのタスクオブジェクトが生成された後に一括構築する
Next i
‘ — 依存関係(リンク)の一括構築フェーズ —
‘ タスクIDが確定した後にリンクを張ることで、計算エンジンの混乱を防ぐ
For i = 2 to 500
activeProj.Tasks(i).TaskDependencies.Add From:=activeProj.Tasks(i – 1), Type:=pjLinkFS
Next i
‘ — 【トランザクション終了】 —
‘ 3. 計算エンジンの再開と「強制同期(フラッシュ)」
Application.Calculation = originalCalculation
‘ ここで手動から自動に戻しただけでは、キューが溜まったままになることがあるため
‘ 明示的にプロジェクト全体を再計算させる
activeProj.Recalculate
Application.ScreenUpdating = originalScreenUpdating
Application.DisplayAlerts = True
Debug.Print “[INFO] 処理完了. 実行時間: ” & Format(Timer – startTime, “0.00”) & “秒”
Exit Sub
ErrorHandler:
‘ =========================================================================
‘ 異常系ハンドリング: 何があっても必ず計算エンジンを復元する
‘ =========================================================================
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “VBAバッチ処理エラー”
‘ 復元処理
On Error Resume Next
Application.Calculation = originalCalculation
Application.ScreenUpdating = originalScreenUpdating
Application.DisplayAlerts = True
Debug.Print “[ERROR] 異常終了により計算エンジンを強制復元しました。”
End Sub
—
連携時の注意点:データベースやファイル連携の勘所
実務において、このコードを基幹データベース(SQL Server等)やExcel連携ツールに組み込む際、以下の罠に注意してほしい。
1. WBSアウトライン(インデント)の罠
タスクのインデント(レベル上げ・下げ)を行うプロパティ(`Outdent` / `Indent`)は、実行するたびに親タスクのサマリー計算が走る。
大量のタスクを一括追加する場合は、まずフラットな状態で全タスクとリンクを生成し、最後にまとめてアウトライン構造を構築するか、WBSコード順に確実に上から下へ構築すること。
2. カレンダーIDの不整合
外部からリソースやタスクを持ち込む際、Project側のベースカレンダー(「標準」「24時間」など)の名称やIDが一致していないと、計算再開時に期間が狂う。`Task.Calendar` プロパティには明示的に有効なカレンダー名をバインドする設計にすること。
—
チーフアーキテクトからの提言
VBAを書くということは、単に動くコードを書き散らすことではない。対象とするアプリケーションの「ライフサイクル」と「内部エンジン」をハックし、コントロール下に置くことだ。
今回紹介した `Application.Calculation` の制御とエラーハンドリングのパターンは、Project VBAの信頼性を担保するための「基本にして至高の作法」である。
君たちの開発現場にこの設計を取り入れ、無駄なフリーズやスケジュール崩壊の恐怖から解放された、堅牢で美しい自動化ツールを構築してほしい。健闘を祈る。
