【テクニカル・上級編】【実務中級】「ASAP」と「開始日固定」の切り替えをVBAでトグル実装する:ツールバー連携のUI設計 – Project VBA解析バイブル

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

【極限の知見】MS Project スケジュールエンジンの制約制御:ASAP/開始日固定トグル実装のアーキテクチャ

プロジェクト管理の現場において、スケジュールの「柔軟性」と「硬直性」のバランスを制御することは、単なるデータ入力ではない。それは、プロジェクト・スケジューリング・エンジンというブラックボックスに対する、高度な操作介入である。

多くの開発者が、`Task.ConstraintType` プロパティを書き換えるだけの単純なスクリプトを記述して満足している。しかし、数千タスク規模の複雑なネットワークを持つプロジェクトにおいて、その「単純な操作」は、計算エンジンの再計算(Recalculation)を誘発し、システム全体のパフォーマンスを著しく低下させる。

本稿では、ASAP(As Soon As Possible)と開始日固定(Start No Earlier Than)を、ツールバー(Ribbon)から一撃で切り替えるトグル実装について、メモリ管理、計算エンジンの最適化、そしてWindows APIを用いたUI制御の観点から、極限まで深掘りしていく。

1. スケジュールエンジンの挙動と制約の性質

まず、我々が制御しようとしている対象の正体を定義しておく。

  • ASAP (pjTaskConstraintAsSoonAsPossible): タスクは先行タスクの完了後、またはプロジェクト開始直後に可能な限り早く開始される。これは「制約なし」に近い状態であり、ネットワークの変動に対して最も高い柔軟性を持つ。
  • 開始日固定 (pjTaskConstraintStartNoEarlierThan): 指定された日付より前には開始できないという「硬直的な制約」である。実務上、これは「この日までは絶対に動かせない」というマイルストーンやリソースの制約を意味する。

これらをトグル(切り替え)する場合、単にプロパティを書き換えるだけでは不十分だ。なぜなら、制約を変更した瞬間に、MS Projectはプロジェクト全体のクリティカルパスを再計算しようとするからだ。

2. パフォーマンスを最大化する「バッチ処理」の設計思想

大量のタスクに対してループ処理を行う際、初心者は以下のような過ちを犯す。

' 【アンチパターン】各ループで再計算が発生し、致命的な遅延を招く
For Each t In Selection.Tasks
    t.ConstraintType = pjTaskConstraintStartNoEarlierThan
Next t

これでは、タスクごとに計算エンジンがフル稼働し、指数関数的な時間増大を招く。プロのアーキテクトは、「計算の局所化」と「一括更新」を徹底する。

最適化戦略

1. Calculationモードの制御: 計算を一時的に手動(Manual)に切り替える。
2. ScreenUpdatingの抑制: 描画負荷を排除する。
3. 計算の明示的実行: 全ての制約変更が完了した後に、一度だけ `Calculate` を呼び出す。

3. 実装:高信頼性トグル・エンジン

以下に、メモリリークを防止し、エラーハンドリングを完備した実装例を示す。ここでは、COMオブジェクトのライフサイクルを意識し、明示的な解放を行っている。

Option Explicit

' Windows APIの宣言: UIのフォーカス状態を確認し、ユーザーによる干渉を防ぐための準備
' (大規模な自動化プロセスにおいて、ユーザーの不意な操作によるメモリ破壊を防ぐ)
Private Declare PtrSafe Function GetForegroundWindow Lib "useradv.dll" () As LongPtr

''' <summary>
''' 選択されたタスクの制約条件をASAPと開始日固定の間でトグルする
''' </summary>
Public Sub ToggleTaskConstraint_Pro()
    Dim targetTasks As TaskSelection
    Dim t As Task
    Dim currentConstraint As PjTaskConstraint
    Dim nextConstraint As PjTaskConstraint
    Dim isChangingToFixed As Boolean
    
    ' 1. オブジェクトの取得とバリデーション
    On Error GoTo ErrorHandler
    Set targetTasks = Application.TaskSelection
    
    If targetTasks.Count = 0 Then
        MsgBox "タスクが選択されていません。", vbExclamation, "Error"
        Exit Sub
    End If

    ' 2. パフォーマンス最適化の開始
    ' 計算エンジンと描画を停止し、オーバーヘッドを最小化する
    Application.Calculation = pjCalculationManual
    Application.ScreenUpdating = False

    ' 3. 状態判定 (最初の選択タスクを基準にトグル方向を決定)
    ' ※実務上、選択範囲内で制約が混在している場合の挙動は設計思想に基づく
    Set t = targetTasks.Item(1)
    If t.ConstraintType = pjTaskConstraintAsSoonAsPossible Then
        isChangingToFixed = True
        nextConstraint = pjTaskConstraintStartNoEarlierThan
    Else
        isChangingToFixed = False
        nextConstraint = pjTaskConstraintAsSoonAsPossible
    End If

    ' 4. バッチ更新プロセス
    ' ループ内では計算を行わず、プロパティの書き換えに徹する
    For Each t In targetTasks
        If Not t Is Nothing Then
            If isChangingToFixed Then
                ' 開始日固定へ:現在の開始日を維持しつつ制約を適用
                t.ConstraintType = nextConstraint
                ' t.Start = t.Start ' 必要に応じて日付の再固定を行う
            Else
                ' ASAPへ:制約を解除
                t.ConstraintType = nextConstraint
            End If
        End If
    Next t

    ' 5. 計算の実行とUIの復元
    ' 全ての変更が完了した後に、一括で計算エンジンを回す
    Application.Calculation = pjCalculationAutomatic
    Application.Calculate
    Application.ScreenUpdating = True

    MsgBox "制約条件の切り替えが完了しました。", vbInformation, "Success"

CleanUp:
    ' 6. メモリの明示的解放 (COMオブジェクトのライフサイクル管理)
    ' これを行わないと、大規模プロジェクトにおいてメモリリークの原因となる
    Set targetTasks = Nothing
    Set t = Nothing
    Exit Sub

ErrorHandler:
    ' エラー発生時も、計算モードや描画設定を必ず元に戻す(システムの整合性維持)
    Application.Calculation = pjCalculationAutomatic
    Application.ScreenUpdating = True
    MsgBox "予期せぬエラーが発生しました: " & Err.Description, vbCritical, "Fatal Error"
    Resume CleanUp
End Sub

4. UI設計:Ribbon XMLとの連携

この機能を実務で「武器」にするには、ツールバー(Ribbon)への統合が不可欠だ。VBAの `CommandBar` はレガシーであり、現代のエンジニアリングとしては Ribbon XML を用いたカスタムUI設計を推奨する。

XML定義内では、`getEnabled` コールバックを使用して、タスクが選択されていない場合にボタンをグレーアウトさせるなど、ユーザーへのフィードバックを制御する。

<!-- Ribbon XML Snippet -->
<button id="btnToggleConstraint" 
        label="制約トグル (ASAP/Fixed)" 
        size="large" 
        onAction="ToggleTaskConstraint_Pro" 
        getEnabled="GetToggleEnabled" />

5. アーキテクトの視点:レガシー環境とシステム間連携

このツールを社内標準システムとして配布する場合、以下の2点に留意せよ。

1. オブジェクトのライフサイクルとメモリ

MS ProjectのVBAはCOM(Component Object Model)上で動作している。特に外部アプリケーション(Excelや独自の.NETアプリケーション)からProjectを操作する場合、`Set objProject = Nothing` による明示的な解放を怠ると、バックグラウンドで `WINWORD.EXE` や `MSPROJECT.EXE` のプロセスがゾンビ化し、サーバーリソースを食いつぶす。

2. Windows API による堅牢性の向上

大規模なバッチ処理中、ユーザーが不用意にウィンドウを操作すると、内部状態が不安定になる。`GetForegroundWindow` 等を用いて、現在実行中のプロセスがMS Projectであることを確認し、必要に応じて `SendMessage` でメッセージキューを制御する設計は、エンタープライズ級の自動化ツールには必須の作法である。

結論

「ASAPと開始日固定を切り替える」という一見単純なタスクの裏側には、計算エンジンの負荷制御、COMオブジェクトの管理、そしてユーザー体験(UX)を左右するUI設計が潜んでいる。

単に「動くコード」を書くのではなく、「大規模環境でも、極限のパフォーマンスを維持し、システムの整合性を崩さないコード」を書くこと。それこそが、伝説的なアーキテクトへの唯一の道である。

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