【テクニカル・上級編】【上級者向け】Projectの計算エンジンをバイパスしてカスタムフィールドを更新する手法 – Project VBA解析バイブル

スポンサーリンク
スポンサーリンク

計算エンジンの呪縛を解く:MS Projectにおけるカスタムフィールド更新の極限最適化

MS Projectを用いた大規模プロジェクト管理システムの構築において、シニアエンジニアが必ず直面する「壁」がある。それは、数千、数万のタスクやリソースが複雑に絡み合った依存関係(Dependency Tree)の中で、カスタムフィールドを更新しようとした瞬間に発生する、絶望的なまでのパフォーマンスの低下だ。

多くの開発者は、`Application.Calculation = pjCalculationManual` を設定することで、この問題を解決したつもりになる。しかし、それはあくまで「初歩的な回避策」に過ぎない。計算エンジンが持つ再帰的な計算トリガー、COMインターフェースを介したオブジェクトへのアクセスオーバーヘッド、そしてメモリ上に展開される計算ツリーの肥大化。これらを知り尽くさない限り、真に堅牢で高速なシステムは構築できない。

本稿では、Projectの計算エンジンを実質的に「バイパス」し、計算負荷を最小化しながらカスタムフィールドを高速に書き込む、アーキテクトレベルの知見を伝授する。

1. なぜ「Manual Calculation」だけでは不十分なのか

Projectの計算エンジンは、単なる計算機ではない。あるフィールドの値が書き換わった瞬間、そのフィールドを参照しているすべての依存関係をスキャンし、再計算の必要性を判定する「インテリジェントなエンジン」だ。

問題は、カスタムフィールドに「数式(Formula)」が設定されている場合、あるいは「数式を参照している別のフィールド」が存在する場合である。たとえ `pjCalculationManual` に設定していても、COMオブジェクトを通じて値を書き込む操作そのものが、内部的なイベント(`OnFieldChange` 等)を誘発し、エンジンの再計算準備プロセスを走らせてしまうことがある。

特に、大量のタスクに対してループ処理を行う際、この「微細なトリガー」の累積が、指数関数的な遅延を招く。我々が目指すべきは、エンジンの「思考」を停止させ、純粋な「データ書き込み」のみを完遂させることである。

2. 戦略的アプローチ:計算ツリーのデカップリング

極限のパフォーマンスを引き出すための戦略は、以下の3点に集約される。

1. データ型の分離(Decoupling Data Types): 計算式を持つフィールド(Formula Field)に直接値を書き込もうとするのは愚策である。書き込み専用の「Raw Dataフィールド(TextやNumber)」を用意し、計算は一括で行う。
2. 書き込みのバッチ化(Batching Updates): プロパティへのアクセス回数を最小化する。
3. UIスレッドの凍結(UI Thread Freezing): Windows APIを用いて、描画更新によるオーバーヘッドを物理的に遮断する。

3. 実装:極限のフィールド更新エンジン

以下に、大規模データ処理に特化した、カスタムフィールド更新用のコアモジュールを示す。ここでは、単なるVBAの範疇を超え、メモリ管理とプロセス制御を意識した設計を行っている。

Option Explicit

' Windows API: 描画更新の強制停止(UIのフリーズによる高速化)
' 非常に強力だが、使用後は必ず解除しなければならない。
Private Declare PtrSafe Function LockWindowUpdate Lib "user32" (ByVal hwndLock As LongPtr) As Long

''' <summary>
''' Projectの計算エンジンと描画プロセスを制御し、
''' カスタムフィールドを極限まで高速に更新する。
''' </summary>
Public Sub HighSpeedFieldUpdate()
    Dim app As MSProject.Application
    Set app = MSProject.Application
    
    Dim t As Task
    Dim targetField As Long
    ' 例として Text1 を使用。実際にはプロジェクト定義に合わせた定数を使用すること
    targetField = pjText1 
    
    On Error GoTo ErrorHandler

    ' --- 1. 環境の凍結 ---
    ' 計算モードをマニュアルへ。これだけでは不十分。
    app.Calculation = pjCalculationManual
    app.ScreenUpdating = False
    
    ' Windows APIによるウィンドウロック(描画リソースの解放)
    ' Projectのメインウィンドウハンドルを取得してロックする
    LockWindowUpdate app.hWnd
    
    ' --- 2. 高速書き込みフェーズ ---
    ' オブジェクトの参照コストを下げるため、TaskCollectionを直接回す
    ' 大規模データの場合、Taskオブジェクトの生成を最小限にする必要がある
    
    Debug.Print "Update Started: " & Now
    
    ' 実践的なテクニック:
    ' 計算式を含むフィールドではなく、必ず「計算結果を格納するための純粋なText/Numberフィールド」を更新すること
    For Each t In app.ActiveProject.Tasks
        If Not t Is Nothing Then
            ' 直接プロパティにアクセス。
            ' ここで複雑なロジックを挟む場合は、事前にメモリ上の配列(Variant Array)で
            ' 計算を済ませてから、一気に書き込むのが定石である。
            t.Text1 = "DATA_" & t.ID ' 擬似的なデータ更新
        End If
    Next t

    ' --- 3. 環境の復元と再計算のトリガー ---
    ' 全ての書き込みが完了した「後」に、一度だけ計算を許可する
    app.Calculation = pjCalculationAutomatic
    app.ScreenUpdating = True
    LockWindowUpdate 0 ' APIによるロック解除

    Debug.Print "Update Completed: " & Now
    MsgBox "更新完了。計算エンジンを再起動しました。", vbInformation
    Exit Sub

ErrorHandler:
    ' エラー発生時でも、必ずロックを解除し、計算モードを戻す(極めて重要)
    app.Calculation = pjCalculationAutomatic
    app.ScreenUpdating = True
    LockWindowUpdate 0
    MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End Sub

4. アーキテクトの視点:メモリとライフサイクル

シニアエンジニアがコードを読む際、単に「動くか」ではなく「リソースをどう扱っているか」を見る。

オブジェクトの明示的解放

上記のコードでは `For Each t In app.ActiveProject.Tasks` を使用しているが、もしループ内でさらに `Resource` オブジェクトや、別の `Sub` を呼び出してオブジェクトを生成している場合、ループの終わりに `Set t = Nothing` を明示的に行う、あるいはスコープを極限まで狭めることが、メモリリーク(Projectプロセスが肥大化し、最終的にクラッシュする現象)を防ぐ鍵となる。

計算式の設計(The “Shadow Field” Pattern)

私が設計する大規模システムでは、以下のパターンを徹底する。

1. `Field_Raw` (Text/Number): VBAから直接書き込むフィールド。計算式は一切持たない。
2. `Field_Calculated` (Formula): `[Field_Raw] * 1.1` のような式を持つフィールド。

VBAは `Field_Raw` だけを高速に書き込み、最後に `app.Calculation = pjCalculationAutomatic` を叩くことで、Projectの計算エンジンに「一括での再計算」を委ねる。これにより、書き込み中の「書き込み→即再計算」という最悪の $O(n^2)$ シナリオを回避し、$O(n)$ の線形時間での更新を実現できる。

結論

Project VBAにおけるパフォーマンスの追求は、単なるコードの書き換えではない。それは、「Projectという巨大な計算エンジンの動作原理を理解し、その動作をいかに制御(コントロール)するか」という、システムアーキテクチャの戦いである。

計算エンジンを敵とするのではなく、計算のタイミングを完全に支配下に置くこと。それが、伝説的なシステムを構築するエンジニアに求められる資質である。

タイトルとURLをコピーしました