MS Projectの限界を突破せよ:カスタムフィールドに「VBAの魂」を宿す擬似UDF実装術
こんにちは。プロジェクト管理の自動化、その深淵へようこそ。
MS Projectを使っていると、必ずと言っていいほど「壁」にぶつかる瞬間があります。それは、「カスタムフィールドの数式(Formula)では、どうしても計算できない複雑なロジックがある」という壁です。
「タスクの期間と、リソースのスキルレベルと、さらに外部のCSVデータを組み合わせて、独自の『プロジェクト難易度スコア』を出したい」……。
標準の数式機能は、Excelの関数のように強力に見えますが、実は非常に限定的なロジックしか持てません。条件分岐(If文)は書けても、複雑なループ処理や、オブジェクト間の高度な参照は不可能です。
今日は、その壁を鮮やかに飛び越えるテクニックを伝授しましょう。テーマは、「カスタムフィールドにVBAのユーザー定義関数(UDF)を擬似的に組み込む」、いわばイベント駆動型擬似計算エンジンの実装です。
ここをクリアすれば、あなたはProject VBAの「単なる操作自動化」の段階を卒業し、「システム構築」の領域へと足を踏み入れることになります。準備はいいですか?
1. なぜ「数式」だけでは足りないのか?(限界の正体)
MS Projectのカスタムフィールドに設定できる数式は、あくまで「その行(タスクやリソース)が持つプロパティ」を組み合わせるだけのものです。
- できること: `[Duration] * [Cost]` のような単純な算術計算。
- できないこと:
- 「関連する後続タスクの全期間の合計」を算出する(ループ処理が必要)。
- 「リソースの特定の属性」を複雑な条件で判定する。
- 外部ファイルやデータベースの値を参照する。
これらを解決するには、数式に頼るのではなく、「値が変わった瞬間に、VBAが裏側で計算して、結果をカスタムフィールドに直接書き込む」というアプローチが必要になります。
2. アーキテクチャ:イベント駆動型「擬似計算」の仕組み
私たちが実装するのは、以下のようなサイクルを持つ仕組みです。
1. トリガー(Event): ユーザーがタスクの期間や名前を変更する。
2. 検知(Interceptor): Projectの「イベント」がその変更をキャッチする。
3. 計算(Logic): VBAの強力なロジック(UDF相当)が走り、複雑な計算を行う。
4. 反映(Injection): 計算結果を、あらかじめ用意しておいたカスタムフィールド(Text1など)に書き込む。
これにより、ユーザーから見れば「数式を入れたわけではないのに、値を変えるとカスタムフィールドが自動で更新される」という、まるでUDF(ユーザー定義関数)のような挙動が実現します。
3. 実践:難易度スコアを自動計算するエンジンの実装
では、具体的なコードを見ていきましょう。
例として、「タスクの期間が長く、かつリソースのコストが高い場合に、独自の『リスクスコア』を算出する」というシナリオを想定します。
ステップ1:準備
MS Projectの画面上で、適当なテキストフィールド(例:`Text1`)を「リスクスコア」として使う準備をしておいてください。
ステップ2:コードの実装
このコードは、標準モジュールではなく、`ThisProject` モジュールに記述する必要があります。ここが最大のポイントです。プロジェクト全体を見守る「監視役」をここに配置するのです。
'==============================================================================
' モジュール: ThisProject
' 目的: タスク変更を検知し、カスタムフィールドへ計算結果を自動注入する
'==============================================================================
Option Explicit
' プロジェクト内のタスクが変更されたときに自動的に実行されるイベント
Private Sub Project_TaskChange(ByVal Task As Task)
On Error GoTo ErrorHandler
' 1. 無限ループ防止策(重要!)
' 値を書き込む行為自体が「変更イベント」を発生させてしまうため、
' 一時的にイベントを抑制する仕組みが必要ですが、Project VBAでは
' 直接的なApplication.EnableEvents = Falseが使えない場合があるため、
' 静的変数を用いた「再入防止フラグ」を使用するのがプロの技です。
Static isProcessing As Boolean
If isProcessing Then Exit Sub
isProcessing = True
' 2. 擬似UDF(計算ロジック)の呼び出し
' ここで複雑な計算を行い、結果をタスクのText1フィールドに書き込む
Task.Text1 = CalculateRiskScore(Task)
isProcessing = False
Exit Sub
ErrorHandler:
isProcessing = False
Debug.Print "Error in TaskChange: " & Err.Description
End Sub
'==============================================================================
' 関数: CalculateRiskScore (擬似UDF)
' 引数: Task - 計算対象のタスクオブジェクト
' 戻り値: String - 計算されたスコア
' 説明: 標準の数式では不可能な、複雑なロジックに基づいたスコア算出
'==============================================================================
Private Function CalculateRiskScore(ByVal t As Task) As String
Dim score As Double
Dim resourceCost As Double
' --- ロジック開始 ---
' A. 期間による重み付け (例: 10日を超えるとスコア上昇)
score = t.Duration / 480 ' 1日=480分とする
' B. リソースコストの加算 (リソースの合計コストを取得)
' 標準の数式では難しい「リソースをループして集計する」処理をここで行う
resourceCost = 0
Dim r As Resource
Dim res As TaskResource
For Each res In t.TaskResources
resourceCost = resourceCost + res.Resource.StdRate ' 標準レートを合算
Next res
score = score + (resourceCost / 100)
' C. 特定の条件によるブースト (例: タスク名に"緊急"が含まれる場合)
If InStr(t.Name, "緊急") > 0 Then
score = score * 2
End If
' --- ロジック終了 ---
' 結果を文字列として返す
CalculateRiskScore = Format(score, "0.0")
End Function
4. ここがプロの分かれ道:陥りやすい「罠」と回避術
この実装を初めて行う方が、必ずと言っていいほど陥る罠が2つあります。これを知っているかどうかが、初級者と上級者の境界線です。
罠その1:無限ループの地獄(Recursion Trap)
コード内の `isProcessing` フラグを見てください。これがないと、恐ろしいことが起こります。
1. ユーザーがタスクを変更する $\rightarrow$ `TaskChange` イベント発生。
2. VBAが `Task.Text1 = …` と値を書き込む。
3. 「値が書き込まれた」ので、再び `TaskChange` イベントが発生する。
4. 手順2に戻る。
結果、Excel/Projectがフリーズするか、クラッシュします。これを防ぐために、「今、計算中ですよ」というフラグを立てて、計算中のイベントを無視する処理が不可欠です。
罠その2:パフォーマンスの重み
`Project_TaskChange` は、ユーザーが何かを入力するたびに、猛烈な勢いで呼び出されます。
もし `CalculateRiskScore` の中で、数万行のデータをスキャンするような重い処理を書くと、ユーザーは「入力するたびに画面が止まる」というストレスを感じることになります。
- 対策: 計算ロジックは極力軽量に。
- 対策: 「特定のフィールドが変わった時だけ計算する」という条件分岐を最初に入れて、無駄な計算を徹底的に排除してください。
5. まとめ:エンジニアとしての次の一歩
今回紹介した手法は、MS Projectを単なる「スケジュール表」から、「独自のビジネスロジックを備えたインテリジェントな管理ツール」へと進化させる強力な武器です。
1. イベント(Event)で変化を捉え、
2. VBA(Logic)で限界を超えた計算を行い、
3. カスタムフィールド(Field)にその結果を刻む。
このサイクルをマスターすれば、あなたはもう「数式が書けないから諦める」なんてことはありません。
最初はエラーが出るかもしれません。無限ループで止まるかもしれません。でも、それはあなたが「オブジェクトのライフサイクル」に深く触れた証拠です。エラーメッセージを愛し、デバッグを楽しんでください。
そこを乗り越えた時、あなたの書くコードは、プロジェクトを動かす真のエンジンへと昇華するはずです。
応援していますよ。頑張ってください!
