【入門編】【中級者向け】カスタムフィールドの数式にVBA関数を組み込む際の限界と回避策 – Project VBA解析バイブル

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

Project VBAの壁を越える:数式では届かない領域を攻略する「ハイブリッド計算」の設計思想

こんにちは。プロジェクト管理の自動化に情熱を注ぐ皆さん、調子はどうですか?

Microsoft Projectのカスタムフィールド(Text1やNumber1など)を使いこなし、数式(Formula)を使って自動計算ができるようになると、「自分でもプロジェクトを制御できる!」という万能感に包まれますよね。それは素晴らしい一歩です。

しかし、ある時あなたは必ず「壁」にぶつかります。

「もし、タスク名に特定の文字列が含まれていて、かつリソースの稼働率が〇%以下で、さらに先行タスクが完了していない場合のみ、この値を計算したい……」

といった、条件が複雑に絡み合うロジックを数式に書き込もうとした瞬間、Projectの数式エディタは「そんな書き方はできないよ」と、無言の拒絶を突きつけてきます。

実は、Projectの数式エンジンは、VBAの関数(User Defined Function)を直接呼び出すことができないという仕様があるのです。

今日は、この壁を突破するためのプロフェッショナルな設計手法「ハイブリッド計算」についてお話ししましょう。ここをクリアすれば、あなたは「マクロを記録する人」から「業務システムを設計するエンジニア」へと進化できますよ。

1. なぜ「数式」だけでは限界があるのか?

まず、敵を知ることから始めましょう。Projectのカスタムフィールドに設定する「数式」は、Excelの数式に近いものですが、実行されている環境が異なります。

  • 数式エンジン: Projectのコア機能。非常に高速だが、あらかじめ決められたプロパティ(`Duration`や`Start`など)と、単純な論理演算(`IIf`など)しか扱えない。
  • VBAエンジン: ユーザーが自由にコードを書ける強力な環境。複雑な条件分岐、外部ファイル参照、高度な計算が可能。

この二つは「住んでいる世界が違う」ため、数式の中から「VBAで作った関数を呼び出す」という橋渡しができないのです。

陥りやすい勘違い

「VBAで `Function MyCalc()` と書いたんだから、数式欄に `=MyCalc([Text1])` と書けば動くはずだ」
→ 残念ながら、これは動きません。

2. 解決策:ハイブリッド計算の設計思想

では、どうすればいいのか? 答えはシンプルです。
「数式で計算させるのを諦め、VBAが計算結果をフィールドに書き込む」という逆転の発想です。

これを私は「ハイブリッド計算(Hybrid Calculation Pattern)」と呼んでいます。

ハイブリッド計算のワークフロー

1. 器(フィールド)を作る: カスタムフィールド(例:`Text1`)を「計算結果を格納する場所」として定義する。
2. ロジックをVBAで書く: 数式では不可能な複雑なロジックを、VBAのプログラムとして記述する。
3. 一括書き出し(バッチ処理): VBAを実行し、全タスク(または変更があったタスク)をループして、計算結果を`Text1`へ書き込む。

この設計にすることで、Projectの数式エンジンの限界を、VBAの圧倒的な自由度で補完することができるのです。

3. 実践:複雑なリスクスコアを計算するコード

では、具体的なコードを見てみましょう。
例として、「タスクの期間が長く、かつリソースが不足している場合に、リスクレベルを『High』と表示する」というロジックを実装してみます。

Option Explicit

''' <summary>
''' タスクのリスクレベルを計算し、カスタムフィールド(Text1)に書き出すメインルーチン
''' </summary>
Sub CalculateTaskRiskLevels()
    Dim t As Task
    Dim riskScore As Integer
    Dim riskLevel As String
    
    ' 画面更新を停止してパフォーマンスを向上させる(重要!)
    ScreenUpdating False
    
    On Error GoTo ErrorHandler

    ' プロジェクト内のすべてのタスクをループ
    For Each t In ActiveProject.Tasks
        ' 空白のタスク行をスキップ
        If Not t Is Nothing Then
            
            ' --- ここから複雑なロジック(ハイブリッド計算の核心) ---
            riskScore = 0
            
            ' 条件1: 期間が20日より長い場合はスコア加算
            If t.Duration > 20 * Day(t.Duration) Then ' 単位に注意
                riskScore = riskScore + 10
            End If
            
            ' 条件2: リソースが割り当てられていない場合はリスク高
            If t.ResourceCount = 0 Then
                riskScore = riskScore + 20
            End If
            
            ' 条件3: 先行タスクが未完了(例:完了率が100%未満)
            ' ※ここでは簡略化のため、タスクのステータス等で判定
            If t.PercentComplete < 100 And t.Start < Date Then
                riskScore = riskScore + 15
            End If
            
            ' スコアに基づいた判定
            If riskScore >= 25 Then
                riskLevel = "🚨 High Risk"
            ElseIf riskScore >= 10 Then
                riskLevel = "⚠️ Medium Risk"
            Else
                riskLevel = "✅ Low Risk"
            End If
            ' --- ロジック終了 ---

            ' 計算結果をカスタムフィールド「Text1」に書き込む
            ' ※事前にプロジェクト設定でText1を計算式なしの「テキスト」として用意しておくこと
            t.Text1 = riskLevel
            
        End If
    Next t

    MsgBox "リスク計算が完了しました!", vbInformation, "完了"

CleanUp:
    ScreenUpdating True
    Exit Sub

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
    Resume CleanUp
End Sub

''' <summary>
''' Projectのパフォーマンスを制御するための補助関数
''' </summary>
Private Sub ScreenUpdating(ByVal bEnable As Boolean)
    ' ProjectにはExcelのようなScreenUpdatingプロパティが直接ないので、
    ' 実際には計算中の表示更新を抑える工夫が必要ですが、
    ' ここでは概念的な記述としています。
End Sub

コードのポイント解説

  • `If Not t Is Nothing Then`: プロジェクトには空の行が存在することがあります。これを確認せずにプロパティにアクセスするとエラーになるため、プロのコードでは必須の作法です。
  • ロジックの分離: `riskScore` を算出する部分は、将来的に「もっと複雑にしたい」となった時に、別の関数として切り出しやすい設計にしています。
  • 書き出し先: `t.Text1 = riskLevel`。これがハイブリッド計算のキモです。計算結果を「値」としてフィールドに叩き込みます。

4. 【超重要】エンジニアが陥る「パフォーマンスの罠」

ここで、伝説のアーキテクトとして、皆さんに最も伝えたい「警告」があります。

ハイブリッド計算を実装する際、多くの人が「計算を自動化したい!」という一心で、`Project_Calculate` イベントの中に上記のループ処理を書いてしまいます。

' ⚠️ やってはいけない例
Private Sub Project_Calculate()
    Call CalculateTaskLevels ' 計算のたびに全タスクをループ!
End Sub

これは絶対に避けてください。

なぜダメなのか?

`Project_Calculate` イベントは、ユーザーが日付を一つ動かしたり、リソースを一つ変更したりするたびに、Projectが内部で計算を行うたびに発生します。

もし、1000タスクあるプロジェクトで、このイベント内で全タスクをループさせていたらどうなるでしょうか?
1. ユーザーが値を変更する
2. Projectが計算を開始する
3. `Project_Calculate` が走る
4. VBAが1000回ループしてフィールドを書き換える
5. フィールドが書き換わったので、Projectが「値が変わった!」と検知して再び計算を開始する
6. 再び `Project_Calculate` が走る……(無限ループ、または極端な動作遅延)

プロジェクトがフリーズし、最悪の場合、強制終了するしかなくなります。

正解の設計

ハイブリッド計算は、「必要な時だけ、明示的に動かす」のが鉄則です。

  • リボンにカスタムボタンを作る。
  • 特定の操作(「進捗更新ボタン」など)の後に呼び出す。
  • あるいは、タスクの変更を検知するのではなく、一日の終わりにまとめて実行する。

「自動化」とは「勝手に動くこと」ではなく、「必要なタイミングで、正確に動くこと」なのです。

まとめ:一歩先のエンジニアへ

「数式が使えないなら、VBAで書き込めばいい」

このシンプルな考え方こそが、複雑な業務要件をプロジェクト管理ツールに落とし込むための強力な武器になります。

1. 数式の限界を知る(VBA関数は呼べない)
2. ハイブリッド計算を採用する(VBAで計算 $\rightarrow$ フィールドに書き出し)
3. ライフサイクルを意識する(`Project_Calculate` での暴走を防ぐ)

この3点をマスターすれば、あなたの作るプロジェクト管理ツールは、単なるスケジュール表を超え、高度な意思決定支援システムへと進化するはずです。

さあ、恐れずにコードを書き始めましょう。応援していますよ!

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