こんにちは。Project VBAの深淵へようこそ。
Microsoft Projectというツールは、単なる「ガントチャート作成ソフト」ではありません。その実体は、数万のタスクが複雑に絡み合う巨大な計算エンジンです。
特に「制約条件(ASAP: できるだけ早く、Must Finish On: 必終、など)」の設定は、スケジュールという名のドミノを倒す最初の1枚になります。誰かが良かれと思って「必終」を設定した瞬間、プロジェクト全体のバッファが消滅する……そんな現場を私は何度も見てきました。
「誰が、いつ、なぜ変えたのか?」
これをブラックボックスにしないことが、PMO(プロジェクトマネジメントオフィス)やリードエンジニアがProject VBAを学ぶ真の目的です。今回は、マクロの記録を卒業し、「イベントハンドリング」というプロの領域へ一歩踏み出してみましょう。
—
1. なぜ「制約条件」のログが重要なのか?
Microsoft Projectの計算エンジンは、以下の優先順位で動いています。
1. カレンダー(稼働時間)
2. 依存関係(リンク)
3. 制約条件(ASAP/ALAP/MSOなど)
ここで恐ろしいのは、制約条件は依存関係よりも優先されることが多いという点です。
例えば、先行タスクが遅れているのに、後続タスクに「指定日に開始(SNET)」がついていると、スケジュールは「不自然に固定」され、真のリスクが見えなくなります。
この「犯人捜し」ではなく「原因究明」のために、VBAで自動的に足跡を残す仕組みを構築しましょう。
—
2. 実装のキモ:TaskChangeイベントを使いこなす
一般的なExcel VBAでは「ボタンを押して実行」するマクロが多いですが、ログ出力においては「ユーザーが値を書き換えた瞬間に動く」イベント駆動型のコードが必要です。
今回使用するのは、`Project_BeforeTaskChange` というイベントです。
準備:カスタムフィールドをログ置き場にする
まず、Project側で「テキスト1(Text1)」などのカスタムフィールドを「変更履歴」という名前に変えておきましょう。ここに「2023/10/27: ASAP -> MSO (By User)」といった文字列を流し込みます。
—
3. 極限の知見を込めたログ出力コード
このコードは、`ThisProject` モジュールに記述します。標準モジュールではない点に注意してください。
‘
‘ Project VBA: Constraint Change Auditor
‘ 役割: 制約条件の変更を検知し、カスタムフィールドに変更履歴を刻む
‘
Option Explicit
‘ タスクの変更前に実行されるイベント
Private Sub Project_BeforeTaskChange(ByVal tsk As Task, ByVal Field As PjField, ByVal NewVal As Variant, Cancel As Boolean)
‘ 監視対象が「制約条件の種類 (pjTaskConstraintType)」であるか判定
If Field = pjTaskConstraintType Then
‘ 現在の(変更前の)値を取得
Dim oldConstraint As String
oldConstraint = GetConstraintName(tsk.ConstraintType)
‘ 新しく設定されようとしている値を取得
Dim newConstraint As String
newConstraint = GetConstraintName(CInt(NewVal))
‘ 値が実際に異なる場合のみログを記録(パフォーマンス対策)
If oldConstraint <> newConstraint Then
Call WriteAuditLog(tsk, oldConstraint, newConstraint)
End If
End If
End Sub
‘ 制約条件の列挙型を分かりやすい文字列に変換するヘルパー関数
Private Function GetConstraintName(ByVal cType As Long) As String
Select Case cType
Case pjAsap: GetConstraintName = “ASAP (できるだけ早く)”
Case pjAlap: GetConstraintName = “ALAP (できるだけ遅く)”
Case pjFnet: GetConstraintName = “FNET (指定日以降に終了)”
Case pjFnlt: GetConstraintName = “FNLT (指定日以前に終了)”
Case pjMfo: GetConstraintName = “MSO (必終)”
Case pjMso: GetConstraintName = “MSO (必始)”
Case pjSnet: GetConstraintName = “SNET (指定日以降に開始)”
Case pjSnlt: GetConstraintName = “SNLT (指定日以前に開始)”
Case Else: GetConstraintName = “Unknown”
End Select
End Function
‘ カスタムフィールドに変更履歴を追記する
Private Sub WriteAuditLog(ByRef tsk As Task, ByVal oldVal As String, ByVal newVal As String)
Dim logEntry As String
Dim currentLog As String
‘ ログの書式: [日時] ユーザー名: 旧 -> 新
logEntry = “[” & Format(Now, “yyyy/mm/dd HH:mm”) & “] ” & _
Environ(“USERNAME”) & “: ” & oldVal & ” -> ” & newVal
‘ Text1(テキスト1)フィールドをログ格納先として使用
‘ 既存のログがある場合は、改行して先頭に追加(最新が上)
currentLog = tsk.Text1
If currentLog <> “” Then
tsk.Text1 = logEntry & vbCrLf & currentLog
Else
tsk.Text1 = logEntry
End If
‘ ヒント: ここで Debug.Print を使うとイミディエイトウィンドウでも確認できます
Debug.Print “Task: ” & tsk.Name & ” | ” & logEntry
End Sub
—
4. コードの解説と「初心者がハマるポイント」
① `BeforeTaskChange` の魔力
`TaskChange`(変更後)ではなく `BeforeTaskChange`(変更前)を使うのがプロの技です。なぜなら、変更前であれば 「変わる前の値」と「これから変わる値」の両方にアクセスできるから です。これにより、「何から何へ変わったのか」という正確な対比が可能になります。
② `Field` 引数の正体
Projectには数百のフィールドがあります。このイベントは、名前を変えても、期間を変えても発生します。
`If Field = pjTaskConstraintType` という一文を入れないと、何か操作するたびにマクロが走り、動作が非常に重くなってしまいます。「必要な時だけ動く」。これが大規模プロジェクトを扱う際の鉄則です。
③ カスタムフィールドの限界
今回は `Text1` フィールドに書き込んでいますが、Projectのテキストフィールドには文字数制限(255文字)があります。変更が多すぎる場合は、古いログが切れてしまう可能性があります。
真に堅牢なシステムにするなら、外部のテキストファイルやSQLiteなどのデータベースへ書き出すように拡張するのがチーフアーキテクト流です。
—
5. スケジュールを掌握するあなたへ
このマクロを導入すると、チームメンバーは「あ、制約条件を変えたら履歴が残るんだ」と意識するようになります。実は、これが最大の効果です。
ツールによる自動化は、単に作業を楽にするだけではありません。「データの整合性に対する規律」を生むためにあるのです。
VBAの基本をマスターすれば、Projectはただのガントチャートから、プロジェクトの「航海日誌」へと進化します。今回紹介したイベントハンドリングを入り口に、オブジェクトブラウザを覗き、Projectの内部構造をさらに探求してみてください。
「ここをクリアすれば、Project VBAの基本はバッチリですよ。次は、リソースの過負荷を検知するエンジンの作成に挑戦してみましょうか。」
あなたのエンジニアリングが、プロジェクトを成功へ導くことを願っています。
