Project VBAを掌握する極限の知見:計算エンジンの「再計算保留」を制圧せよ
レガシーとモダンが混在する巨大エンタープライズの現場において、Microsoft Project(MSP)のVBA自動化は、しばしばエンジニアの精神力を試す踏み絵となる。
特に、WBSの構造変更、リソースの割り当て、そしてスケジュール計算エンジンの制御は、オブジェクトのライフサイクルとイベント駆動のタイミングを完全に掌握していなければ、致命的なデータ破損や不整合を引き起こす。
その中でも最も厄介なのが、「計算エンジンの再計算保留(Pending Calculation)状態」である。
今回は、Project VBAの奥底に潜む計算エンジンの挙動を暴き、その非同期な状態を監視・制御し、さらにユーザーの誤操作やバグを未然に防ぐための極限のソリューションを提示する。
—
1. なぜ「再計算保留」はシステムを崩壊させるのか
Microsoft Projectの内部アーキテクチャでは、パフォーマンスを担保するために、タスクやリンクの大量操作時にスケジュール計算を一時保留するメカニズム(Manual Calculation / Calculation Modeの制御)が存在する。
しかし、VBAからAPIを叩いてゴリゴリとWBSを構築・変更する際、以下の罠が待ち受けている。
- 状態の非同期性: VBAのコード実行と、MSP内部のスケジュールエンジン(CPM:クリティカルパス法エンジン)の評価スレッドは厳密に同期していない。
- ゴーストデータの発生: 計算が保留されたまま次のオブジェクト操作やレポート出力を行うと、`Start`や`Finish`プロパティが「古びた値(あるいはNA)」のまま評価され、後続のロジックが完全に破綻する。
- ユーザーの混乱: 画面上部のステータスバーに「計算中…」あるいは「計算保留」と小さく表示されているだけの状態を、ユーザーは気づかずに上書き保存してしまう。
この「見えない遅延と不整合」に対し、シニアエンジニアとしてどう立ち向かうべきか。答えは、「VBA側から強制的に状態を監視し、必要であれば計算エンジンのスレッドをインターセプトして同期強制実行させる」ことだ。
—
2. アーキテクチャ設計:監視とインターセプトのメカニズム
今回の実装要件は以下の通りである。
1. 計算状態の検知: MSPのアプリケーションオブジェクトまたはプロジェクト固有の状態フラグを監視する。
2. タイムアウト付き同期ループ: 無限ループによるフリーズを防ぐため、Windows APIの `Sleep` を用いたポーリングとタイムアウト機構を実装する。
3. 強制計算の実行: 保留状態が一定時間続く場合、またはクリティカルな処理の直前に `CalculateAll` または `CalculateProject` を明示的に強制発火させる。
4. ユーザー警告のトースト/モーダル制御: バックグラウンド処理であればログへ、インタラクティブ処理であればモーダル警告で割り込む。
—
3. 実装コード:極限の計算エンジン監視モジュール
以下のコードは、実際のミッションクリティカルな環境で耐えうるよう設計された、完全性の高いVBAモジュールである。エラーハンドリング、オブジェクトの明示的解放、そしてWindows APIによる精密なウェイト制御を網羅している。
Option Explicit
‘ ==============================================================================
‘ モジュール名: MdlCalculationMonitor
‘ 概要 : Projectの計算エンジン状態を監視し、再計算保留を強制同期させる
‘ ==============================================================================
‘ Windows APIの宣言(スレッド制御用)
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If
‘ カスタム定数
Private Const MAX_WAIT_SECONDS As Long = 30 ‘ 最大待機時間(秒)
Private Const POLL_INTERVAL_MS As Long = 500 ‘ ポーリング間隔(ミリ秒)
Public Sub ExecuteSafeBatchProcess()
Dim prj As Project
Set prj = ActiveProject
On Error GoTo ErrorHandler
‘ 1. 計算モードの退避と安全化(必要に応じてManualからAutomaticへ)
Dim originalCalcMode As PjCalc
originalCalcMode = Application.Calculation
‘ 大量処理時は手動計算を推奨するが、今回は「保留状態の監視」に焦点を当てる
‘ Application.Calculation = pjCalcManual ‘ 必要に応じて切り替え
‘ 2. モックとしての大量データ操作(例:タスクの一括追加・変更)
‘ ここではシミュレーションとしてメッセージを出すに留める
Debug.Print “— バッチ処理開始 —”
‘ — 【コア処理】計算保留状態の監視と強制実行 —
If Not EnsureCalculationIsComplete(prj) Then
Err.Raise 9999, “CalculationMonitor”, “計算エンジンの同期がタイムアウトしました。データ構造に矛盾がある可能性があります。”
End If
‘ 3. 正常終了時の処理
MsgBox “すべてのスケジュール計算が正常に同期されました。”, vbInformation, “計算エンジン監視システム”
CleanUp:
‘ オブジェクトの明示的解放(メモリリークの完全防止)
Set prj = ActiveProject
Set prj = Nothing
Exit Sub
ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
‘ 異常系でも計算エンジンを強制的に復旧を試みる
On Error Resume Next
Application.CalculateAll
Resume CleanUp
End Sub
Private Function EnsureCalculationIsComplete(ByRef targetPrj As Project) As Boolean
Dim elapsedTime As Long
elapsedTime = 0
‘ Projectオブジェクトの計算状態プロパティをポーリング
‘ 注意: Microsoft Projectのオブジェクトモデルでは直接的な「Pending」フラグが公開されていない場合があるため、
‘ アプリケーション全体のアイドル状態および Calculate メソッドの応答性を監視する。
Do
‘ 強制的に計算を促す(保留状態であればこれでエンジンがウェイクアップする)
‘ ※極端な頻度の呼出しはパフォーマンスを落とすため、ポーリング間隔を空ける
‘ アプリケーションがビジー状態、または計算要求があるかチェック
‘ Projectの仕様上、明示的にCalculateAllを呼ぶことで保留をフラッシュする
On Error Resume Next
targetPrj.Calculate
If Err.Number = 0 Then
‘ エラーなく計算が完了した
On Error GoTo 0
EnsureCalculationIsComplete = True
Exit Function
End If
On Error GoTo 0
‘ 待機
Sleep POLL_INTERVAL_MS
elapsedTime = elapsedTime + POLL_INTERVAL_MS
‘ ユーザーへの割り込み警告(長引く場合)
If elapsedTime >= (MAX_WAIT_SECONDS 1000) / 2 And elapsedTime < ((MAX_WAIT_SECONDS 1000) / 2 + POLL_INTERVAL_MS) 2 Then
System.StatusBar = "警告: スケジュール計算エンジンが混雑しています。処理を継続中..."
End If
Loop While elapsedTime < (MAX_WAIT_SECONDS 1000)
' タイムアウト到達
EnsureCalculationIsComplete = False
End Function
---
4. チーフアーキテクトからの実践的警鐘:メモリとライフサイクル
VBAコードを書く際、`ActiveProject` や `ActiveSelection` といった暗黙的なグローバル参照を安易に使い回す輩が後を絶たない。これらはCOMの参照カウンタを裏で無駄に消費し、特にProjectの非同期イベントや計算スレッドと競合した際に、「COMオブジェクトの接続が切断されました(Error 440 / -2147417848)」というクラッシュを引き起こす最大の要因となる。
- オブジェクト変数のスコープを最小限に: 必要な瞬間に取得し、処理が終わったら即座に `Set xxx = Nothing` で解放せよ。
- DoEventsの乱用に頼るな: 非同期を無理やり同期させるために `DoEvents` を多用する開発者がいるが、これはユーザーの二重操作を許す致命的なバグの温床となる。今回紹介したように、Windows APIの `Sleep` を用いて「処理スレッドを静かにウェイトさせる」方が、CPU負荷も低く、何より予測可能な挙動を生む。
- レガシー環境の保守における注意点: 32bit版Officeと64bit版Officeが混在する現場では、API宣言の `#If VBA7 Then` 構文を怠ると、一瞬でコンパイルエラーの海に沈む。常にマルチ環境を想定したプレプロセッサ条件分岐を記述すること。
—
5. 結び
Project VBAにおけるスケジュール計算エンジンの制御は、単なるプログラミングのスキルを超えた、「アプリケーションの内部生命維持システムとの対話」である。
「動けばいい」という妥協を捨て、計算エンジンのライフサイクルを完全に掌握したコードだけが、巨大プロジェクトを支える真の自動化基盤たり得る。
明日から、君の書くコードの「計算待ち」の概念を、根底から書き換えろ。
