【極限の知見】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の基本はバッチリですよ。
次は、この監視エンジンを使って「特定の日付以降にタスクが開始されたら警告を出す」といった、より高度なロジックにも挑戦してみてください。
応援しています。また次の現場でお会いしましょう!
