【入門編】プロ向け:Project.TaskConstraintChangeイベントのフックによる制約違反のリアルタイム監視 – Project VBA解析バイブル

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

【極限の知見】Project VBAでスケジュールを「守る」:TaskConstraintChangeによる制約違反の自動ロールバック実装

こんにちは。現場の自動化を支えるエンジニアの皆さん。
今日は、Microsoft Projectの「運用ルール」をコードで強制的に守らせる、少し高度で、かつ非常に実用的なテクニックについてお話ししましょう。

皆さんは、こんな経験はありませんか?
「プロジェクトの計画がガチガチに固まっていて、日付を動かそうとしても全然動かない……」
「調べてみたら、誰かがタスクに『Must Start On(○月○日に開始しなければならない)』なんて強力な制約を付けていた……」

プロジェクト管理において、制約(Constraint)の使いすぎは致命傷になります。本来、スケジュールは動的なものであるべきです。それなのに、ユーザーが手動で「固定日付」を打ち込んでしまうと、スケジュールの柔軟性は失われ、計算エンジンは悲鳴を上げます。

今日は、そんな「ルール違反」をリアルタイムで検知し、即座に正しい状態へと引き戻す「監視エンジン(Watchdog)」の作り方を伝授します。これをマスターすれば、あなたは単なる「マクロ作成者」から、一歩進んだ「システムアーキテクト」へと昇華できるはずですよ。

1. なぜ「マクロの記録」では足りないのか?

多くの初学者は「マクロの記録」からスタートします。しかし、マクロの記録は「ボタンを押した時の操作」を記録するだけで、「何かが起きたこと」を察知することはできません。

今回私たちが使うのは、「イベント(Event)」という仕組みです。
「ユーザーが制約を変更した」という事象(イベント)が発生した瞬間に、VBAがそれをキャッチして、あらかじめ用意しておいたプログラムを起動させる。これが「監視エンジン」の正体です。

この実装には、通常の標準モジュールではなく、「クラスモジュール」という概念が必要になります。ここが最初の、そして最大の壁ですが、ここをクリアすればProject VBAの基本はバッチリですよ。

2. 実装の設計図:監視エンジンの構造

今回の実装は、2つの部品で構成します。

1. 監視用クラスモジュール (`clsConstraintMonitor`):
「耳」の役割を果たします。Projectの動きを常に聞き、制約が変わった瞬間に反応します。
2. 標準モジュール (`modMain`):
「脳」の役割を果たします。監視エンジンを起動させ、ルールを定義します。

実装のロジック

  • ユーザーがタスクの制約を変更する。
  • `TaskConstraintChange` イベントが発火する。
  • プログラムが「その制約は組織のルール(例:ASAP以外は禁止)に違反していないか?」を判定する。
  • 違反していたら、即座に `pjTaskConstraintAsSoonAsPossible`(可能な限り早く)に書き換えてロールバックする。

3. 実装コード:魂のプログラミング

それでは、実際にコードを書いていきましょう。

ステップ1:クラスモジュールの作成

VBAエディタのメニューから `挿入` -> `クラスモジュール` を選択し、オブジェクト名を `clsConstraintMonitor` に変更してください。

' ============================================================
' クラス名: clsConstraintMonitor
' 役割: Projectのイベントを監視し、制約変更を検知する「耳」
' ============================================================
Option Explicit

' Projectアプリケーションのイベントを扱うための宣言
' WithEvents を使うことで、特定のオブジェクトのイベントをフックできます
Private WithEvents App As MSProject.Application

' 監視エンジンを起動するための初期化メソッド
Public Sub Initialize(ByVal targetApp As MSProject.Application)
    Set App = targetApp
End Sub

' タスクの制約(Constraint)が変更された時に自動的に呼ばれるイベント
Private Sub App_TaskConstraintChange(ByVal t As MSProject.Task)
    ' 【重要】無限ループ防止フラグ(後述の解説参照)
    ' ここでは簡略化のため、直接ロジックを書きます
    
    On Error GoTo ErrorHandler

    ' 1. 変更された制約の種類をチェック
    ' 今回のルール: 'Must Start On' や 'Must Finish On' などの強固な制約は禁止!
    ' ASAP (As Soon As Possible) か、それ以外を許可する設計にします。
    
    Select Case t.ConstraintType
        
        ' 強力な制約(Must Start/Finish)が設定された場合
        Case pjTaskConstraintMustStartOn, _
             pjTaskConstraintMustFinishOn, _
             pjTaskConstraintStartNoEarlierThan, _
             pjTaskConstraintStartNoLaterThan
             
            ' ユーザーへの警告(親切な設計)
            MsgBox "【警告】" & vbCrLf & _
                   "タスク「" & t.Name & "」に強力な制約が設定されました。" & vbCrLf & _
                   "スケジュールの柔軟性を保つため、制約を『可能な限り早く(ASAP)』に自動修正します。", _
                   vbExclamation, "ガバナンス・エンジン"

            ' 2. ロールバック(制約を ASAP に強制変更)
            ' ここで制約を書き換えると、再度このイベントが発生するため、
            ' 本来は再入防止策が必要ですが、今回は基本構造を示します。
            t.ConstraintType = pjTaskConstraintAsSoonAsPossible

        Case Else
            ' 許可された制約(ASAPなど)は何もしない
    End Select

    Exit Sub

ErrorHandler:
    Debug.Print "Error in TaskConstraintChange: " & Err.Description
End Sub

ステップ2:標準モジュールの作成

次に、`挿入` -> `標準モジュール` を選択し、以下のコードを記述します。

' ============================================================
' モジュール名: modMain
' 役割: 監視エンジンの起動と制御を行う「司令塔」
' ============================================================
Option Explicit

' 監視用クラスのインスタンスを保持するための変数
' ※ここで宣言しないと、プロシージャ終了時に監視エンジンが消滅してしまいます!
Private Monitor As clsConstraintMonitor

' 監視エンジンを起動するメインルーチン
Sub StartMonitoring()
    ' すでに動いている場合は一度リセット
    StopMonitoring
    
    ' クラスのインスタンスを作成
    Set Monitor = New clsConstraintMonitor
    
    ' Projectアプリケーションを監視対象としてセット
    Monitor.Initialize MSProject.Application
    
    MsgBox "スケジュール監視エンジンが起動しました。" & vbCrLf & _
           "制約違反(Must Start/Finish等)を自動検知します。", vbInformation
End Sub

' 監視エンジンを停止するルーチン
Sub StopMonitoring()
    ' インスタンスを破棄してイベントを解除
    Set Monitor = Nothing
    Debug.Print "監視エンジンを停止しました。"
End Sub

4. プロが教える「落とし穴」と回避策

このコードを動かすと、驚くほどスムーズに動きます。しかし、実戦投入する前に、プロとして絶対に知っておいてほしい「極限の知見」が一つあります。

⚠️ 無限ループの罠(Re-entrancy)

上記のコードの `App_TaskConstraintChange` 内で、`t.ConstraintType = …` と制約を書き換えていますよね?
実は、「制約を書き換える」という操作も、また「制約変更イベント」を発生させます。

1. ユーザーが制約を変える
2. イベント発生!
3. プログラムが制約を書き換える
4. またイベントが発生!(ここが罠)
5. プログラムがまた書き換える……(無限ループ)

これを防ぐには、クラスモジュール内に `Private IsProcessing As Boolean` というフラグを用意し、処理中であれば `Exit Sub` する仕組みを組み込むのが定石です。

' --- クラスモジュール内への追加イメージ ---
Private IsProcessing As Boolean

Private Sub App_TaskConstraintChange(ByVal t As MSProject.Task)
    If IsProcessing Then Exit Sub ' 処理中なら何もしない
    
    IsProcessing = True ' 処理開始フラグを立てる
    
    ' --- ここに本来のロジックを書く ---
    
    IsProcessing = False ' 処理終了
End Sub

この「再入防止(Re-entrancy Guard)」こそが、マクロの記録から脱却し、堅牢なシステムを構築するための境界線です。

5. まとめ:エンジニアとしての次の一歩

お疲れ様でした!
今回実装したのは、単なる自動化コードではありません。組織のルールをシステムとして組み込み、「人間がミスをしても、システムが正しく守ってくれる」という信頼の基盤です。

今回のポイント:

  • イベント(Event)を使うことで、受動的な操作から能動的な監視へ進化できる。
  • クラスモジュールを使うことで、アプリケーションの挙動を制御する「オブジェクト」を作れる。
  • 再入防止(Flag)を忘れないことが、プロフェッショナルなコードへの鍵。

ここをクリアすれば、Project VBAの基本はバッチリですよ。
次は、この監視エンジンを使って「特定の日付以降にタスクが開始されたら警告を出す」といった、より高度なロジックにも挑戦してみてください。

応援しています。また次の現場でお会いしましょう!

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