【入門編】【プロ向け】TaskConstraintChangeイベントを用いた、ユーザーによる「制約条件の勝手な変更」を検知・復元する監視エージェント – Project VBA解析バイブル

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

プロジェクトの秩序を守る守護神:TaskConstraintChangeイベントによる制約条件の監視エージェント実装術

こんにちは。プロジェクトの自動化に取り組んでいる皆さん、日々「計算が合わない」「スケジュールが予期せぬ方向に動いてしまった」という問題と戦っていませんか?

マクロの記録を使って「ボタンを押したら特定の値を書き換える」という段階を卒業し、「システムが自律的に状況を判断し、不正な操作を検知・修正する」という次のステージへ進もうとしているあなたへ。今日は、Microsoft Project VBAにおける「イベント駆動型アーキテクチャ」の真髄をお伝えします。

今回扱うテーマは、ユーザーによる「制約条件(Constraint)の勝手な変更」を検知し、プロジェクトの整合性を守るための監視エージェントの構築です。

なぜ「制約条件」の変更は危険なのか?

Microsoft Projectにおいて、タスクの制約条件(ASAP: なるべく早く、ALAP: なるべく遅く、Must Start On: 指定日に開始、など)は、スケジュールの「心臓部」です。

自動計算エンジン(Critical Path Method)は、タスク間の依存関係に基づいて動的に日付を計算します。しかし、ユーザーが手動で「Must Start On(指定日に開始)」などの強い制約を一つ入れてしまうだけで、そのタスク以降のすべての計算ロジックが「強制的な固定値」に支配され、プロジェクト全体のクリティカルパスが崩壊してしまうことがあります。

これを防ぐには、「変更された後に直す」のではなく、「変更された瞬間に検知して、秩序を取り戻す」仕組みが必要です。

核心技術:イベント・ドリブン(Event-Driven)への転換

多くの初学者は、「定期的にマクロを実行して、間違いがないかチェックする」という手法(ポーリング方式)を選びがちです。しかし、これは非常に効率が悪く、チェックの間にある「不正な操作」を見逃してしまいます。

プロは「イベント(Event)」を使います。
Microsoft Projectには、「タスクの制約が変わった瞬間に、プログラムに通知を送る」という仕組みが備わっています。それが `TaskConstraintChange` イベントです。

監視の仕組みを図解すると

1. 監視エージェント(Class Module)が、Projectの動きを常に「耳」を澄ませて待機。
2. ユーザーが制約を変更。
3. Projectが「おい、制約が変わったぞ!」とイベントを発火。
4. エージェントが即座に検知し、「そのタスクは保護対象か?」を判定。
5. NGであれば、ログを記録し、元の設定へロールバック(復元)。

実装編:監視エージェントの構築

では、実際に動くコードを見ていきましょう。この実装には、2つのステップが必要です。
1. クラスモジュール:イベントをキャッチする「耳」を作る。
2. 標準モジュール:監視を開始・停止する「スイッチ」を作る。

Step 1: クラスモジュールの作成

VBAエディタで「挿入」→「クラスモジュール」を選択し、オブジェクト名を `clsProjectWatcher` に変更してください。

' クラス名: clsProjectWatcher
Option Explicit

' Projectオブジェクトのイベントをキャッチするための宣言
' WithEvents を使うことで、特定のオブジェクトの動きを監視できるようになります
Private WithEvents Pj As MSProject.Application

' 監視を有効にするための初期化メソッド
Public Sub Initialize(App As MSProject.Application)
    Set Pj = App
    Debug.Print "--- 監視エージェント、起動しました ---"
End Sub

' 監視を解除するためのメソッド
Public Sub Terminate()
    Set Pj = Nothing
    Debug.Print "--- 監視エージェント、停止しました ---"
End Sub

' 【ここが核心】タスクの制約条件が変更されたときに自動的に実行されるイベント
Private Sub Pj_TaskConstraintChange(ByVal Task As MSProject.Task)
    
    ' 1. 監視対象外のタスク(例:IDが1未満など)をスキップするロジック
    ' ここでは例として、特定のID(例えばID:5)を「保護対象」とします
    Const PROTECTED_TASK_ID As Integer = 5
    
    If Task.ID = PROTECTED_TASK_ID Then
        
        ' 2. 不正な変更を検知
        ' 本来はASAP(As Soon As Possible)であるべきなのに、それ以外になった場合
        If Task.Constraint <> pjAsSoonAsPossible Then
            
            ' 警告メッセージ(実運用ではログ記録やダイアログに置き換え)
            MsgBox "【警告】タスク ID:" & Task.ID & " の制約条件は変更できません!" & vbCrLf & _
                   "自動的に元の設定(ASAP)に復元します。", vbCritical, "整合性エラー"
            
            ' 3. ロールバック(元の状態に戻す)
            ' ここで再びイベントが発生する「無限ループ」に注意が必要ですが、
            ' 単純な値の書き戻しであれば、Projectの内部処理で制御されます。
            On Error Resume Next
            Task.Constraint = pjAsSoonAsPossible
            On Error GoTo 0
            
            ' ログ出力(イミディエイトウィンドウ)
            Debug.Print "Log: [" & Now & "] Task " & Task.ID & " の不正な変更をロールバックしました。"
        End If
    End If
End Sub

Step 2: 標準モジュールの作成

次に、このエージェントを起動するための通常のモジュールを作成します。

' 標準モジュール
Option Explicit

' エージェントのインスタンスを保持するための変数
' プロシージャが終わっても消えないよう、モジュールレベルで宣言します
Private Watcher As clsProjectWatcher

' 監視を開始するプロシージャ
Sub StartMonitoring()
    ' クラスのインスタンスを作成
    Set Watcher = New clsProjectWatcher
    
    ' Projectアプリケーションを渡して監視を開始
    Watcher.Initialize Application
    
    MsgBox "プロジェクトの整合性監視を開始しました。", vbInformation
End Sub

' 監視を停止するプロシージャ
Sub StopMonitoring()
    If Not Watcher Is Nothing Then
        Watcher.Terminate
        Set Watcher = Nothing
        MsgBox "監視を停止しました。", vbExclamation
    Else
        MsgBox "監視エージェントは起動していません。", vbInformation
    End If
End Sub

プロが教える「陥りやすい罠」と回避術

このコードを動かす際、設計者として絶対に知っておくべき「重み」が2つあります。

1. 無限ループの恐怖(Re-entrancy)

イベントハンドラの中で、そのイベントを発生させる原因となるプロパティ(今回なら `Task.Constraint`)を変更すると、「変更したから、またイベントが発生する」という無限ループに陥ることがあります。
上記のコードでは、`Task.Constraint = pjAsSoonAsPossible` と書き戻していますが、これによって再び `Pj_TaskConstraintChange` が呼ばれます。

  • 対策: 変更後の値が、すでに「正しい値」である場合は、処理を即座に抜ける(`If Task.Constraint = pjAsSoonAsPossible Then Exit Sub`)といったガード節を必ず入れましょう。

2. パフォーマンスへの影響(Overhead)

`TaskConstraintChange` は、ユーザーがマウスで操作するたびに、あるいは大量のタスクをコピー&ペーストした瞬間に、爆発的な回数呼び出されます。
もし、このイベントの中に「重い計算」や「外部データベースへの書き込み」を入れてしまうと、Projectの操作感は極端に悪化し、ユーザーはストレスを感じます。

  • 対策: イベント内では「判定」と「最小限の修正」に徹してください。重い処理が必要な場合は、フラグだけ立てておき、後でまとめて処理する設計にしましょう。

まとめ:ここをクリアすれば、Project VBAの基本はバッチリですよ

いかがでしたでしょうか?
「マクロの記録」は、いわば「決められた手順をなぞる」だけのものです。しかし、今回学んだ「イベント駆動」の考え方は、「システムに知能を持たせる」ための第一歩です。

1. オブジェクトの動きを「イベント」で捉える。
2. クラスモジュールを使って「監視の役割」を分離する。
3. 無限ループやパフォーマンスといった「副作用」を考慮する。

これらを意識できるようになったとき、あなたの書くコードは単なる「自動化ツール」から、プロフェッショナルな「業務アプリケーション」へと進化します。

一歩ずつ、確実に。次はもっと複雑な制約(日付そのものの変更検知など)にも挑戦してみましょう。応援していますよ!

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