【テクニカル・上級編】Projectの「フィルター」をVBAで動的作成する:特定の条件に合致するタスクを即座に抽出する – Project VBA解析バイブル

スポンサーリンク

Projectの「フィルター」をVBAで動的作成する:複雑な条件を瞬時に貫く動的フィルタリングの極意

Microsoft Project(以下、MS Project)におけるVBA開発において、最大のボトルネックは「UIとデータ層の同期コスト」と「オブジェクトモデルの迷宮」にある。

特に、現場から寄せられる「この条件とあの条件を組み合わせて、一瞬で特定のクリティカルパス上の遅延タスクだけを抽出したい」という要求に対し、静的なカスタムフィルターをあらかじめ定義させておくアプローチは、大規模エンタープライズ環境においてはすでに破綻している。ユーザーごとに異なる無数の要件をハードコーディングすることは不可能だからだ。

今回は、`FilterEdit` メソッドを極限までドライに使いこなし、VBAからメモリ上へ動的にフィルター条件を構築・適用するアーキテクチャを解説する。レガシーなMS Projectの制約をねじ伏せ、実務で即座に使えるプロダクション品質のコードを提示しよう。

—

1. MS Projectオブジェクトモデルの闇:`FilterEdit` の実態

MS Projectのフィルターは、Excelのオートフィルターのような軽量なものではない。プロジェクトデータストア内に永続的(あるいは一時的)に定義される「ビューの構成要素」である。

VBAからこれを動的に操作する場合、以下の設計思想を理解していなければならない。

1. グローバルかプロジェクトスコープか: フィルターは `Project` オブジェクトのコレクションに属する。不必要にグローバル(Global.mpr)を汚染してはならない。
2. 既存フィルターの衝突(Collision): 動的生成するフィルター名が既に存在する場合、上書きまたはエラーハンドリングが必要となる。
3. UIの再描画コスト: `ApplyFilter` を実行した瞬間にガントチャートやタスクシートの再描画が発生する。これを抑制しないと、数万行のエンタープライズ計画において致命的なパフォーマンス低下を招く。

危険な罠:オブジェクトのライフサイクルとメモリリーク

VBAのMS Projectラッパーは、背後でCOMオブジェクトとしてC++のコアエンジンを叩いている。`ActiveProject.TaskFilters` のようなプロパティを漫然とループさせると、COMの参照カウンタが適切にデクリメントされず、VBAのプロセスが肥大化する。
不要になった動的フィルターは、処理の終了時に必ずプログラム側で消去(Cleanup)するのがシニアの流儀である。

—

2. 実装:動的フィルター生成・適用エンジンの全コード

以下のコードは、指定した「コスト超過」かつ「残存工数あり」かつ「指定リソースがアサインされている」という、現場で最も需要が高い複合条件を動的に生成し、瞬時にビューへ適用するプロシージャである。

Option Explicit

‘ ==============================================================================
‘ 担当者・コスト超過タスクを動的に抽出し、ビューに適用するメインプロシージャ
‘ ==============================================================================
Public Sub ApplyDynamicTaskFilter()
Dim targetProject As MSProject.Project
Set targetProject = ActiveProject

‘ 1. 定数・検索条件の定義(本来は外部設定やユーザーフォームから動的に受ける)
Const FILTER_NAME As String = “_VBA_Dynamic_Cost_Overrun”
Const TARGET_RESOURCE_NAME As String = “シニアアーキテクト”
Const COST_THRESHOLD As Currency = 1000000# ‘ 100万円超

‘ 2. 既存の同名一時フィルターが存在する場合は安全に削除
Call RemoveExistingFilter(targetProject, FILTER_NAME)

‘ 3. FilterEditメソッドによる動的フィルターの構築
‘ 構文: FilterEdit Name, TaskOrResource, [Create], [OverwriteExisting], [ShowInMenu], _
‘ [Category], [Field1], [Test1], [Value1], [Operation], [Field2], …
On Error GoTo ErrorHandler

Application.ScreenUpdating = False ‘ 描画をロックしてパフォーマンスを極限まで引き上げる

targetProject.TaskFilters.Add Name:=FILTER_NAME

‘ 複雑なAND/OR条件を構築する
‘ 条件1: 実際のコスト > 閾値
‘ 条件2: 残存工数(Remaining Work) > 0
‘ 条件3: リソース名に特定文字列を含む

FilterEdit _
Name:=FILTER_NAME, _
TaskOrResource:=True, _
Create:=False, _
OverwriteExisting:=True, _
ShowInMenu:=False, _
Category:=”コスト管理”, _
Field1:=”コスト”, _
Test1:=pjFieldTestGreaterThan, _
Value1:=CStr(COST_THRESHOLD), _
Operation:=pjAnd, _
Field2:=”残存工数”, _
Test2:=pjFieldTestGreaterThan, _
Value2:=”0h”, _
Operation:=pjAnd, _
Field3:=”リソース名”, _
Test3:=pjFieldTestContains, _
Value3:=TARGET_RESOURCE_NAME

‘ 4. 生成したフィルターを現在のビューに適用
ViewApply Name:=”&Gantt Chart” ‘ ベースビューを確実に保証
FilterApply Name:=FILTER_NAME

‘ ハイライトのみにする場合は ApplyHighlight:=True を使うが、今回は抽出(Filter)とする

MsgBox “動的フィルターの適用が完了しました。”, vbInformation, “Architect Engine”

CleanUp:
Application.ScreenUpdating = True
Set targetProject = Nothing
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “VBA Error”
Resume CleanUp
End Sub

‘ ==============================================================================
‘ 補助関数: 既存の動的フィルターの安全なクリーンアップ
‘ ==============================================================================
Private Sub RemoveExistingFilter(ByRef prj As MSProject.Project, ByVal filterName As String)
Dim flt As MSProject.Filter
On Error Resume Next
Set flt = prj.TaskFilters(filterName)
On Error GoTo 0

If Not flt Is Nothing Then
‘ フィルターが存在する場合は削除してメモリを解放
flt.Delete
End If
Set flt = Nothing
End Sub

—

3. チーフアーキテクトによるコード解説と極限の知見

① `ScreenUpdating = False` の絶対的必要性

MS Projectは、UIと内部データベース(TDB)が密結合している。VBAからフィルター構造を変更し、それをビューに反映させる際、背後で幾重ものレイアウト計算と再描画走査が走る。
数千タスクを持つWBSでこれを無防備に行うと、画面のちらつきだけでなく、COMのメッセージループが飽和し、最悪の場合は「応答なし」ステータスに陥る。必ず処理の冒頭で画面更新を止め、最後に復元させよ。

② `FilterEdit` の引数型の罠

`FilterEdit` の `Value1` や `Value2` などの引数は、一見数値や日付を渡せそうに見えるが、内部的には文字列(Variant/String)として評価される。
例えばコストや工数を扱う際、`CStr()` で明示的に文字列化するか、MS Projectが解釈できるフォーマット(例: `”1000000″` や `”0h”`)に正規化しなければ、フィルター実行時に「型の不一致」エラーですらなく、「条件に一致するデータが0件になる(サイレント失敗)」という最もデバッグが困難な現象を引き起こす。ここには細心の注意を払うべきだ。

③ ガバナンスとクリーンアップ

動的フィルターは強力だが、プログラムが異常終了した際にMS Projectのプロジェクトファイル(.mpp)の内部テーブルにゴミとして残り続けることがある。これを放置するとファイルサイズが肥大化し、破損の原因となる。
実運用システムに組み込む場合は、`WorkbookBeforeClose` やエラーハンドラ内で必ず動的生成したフィルター(例: `_VBA_` から始まるプレフィックスを持つもの)を自動掃討するガーベジコレクション的ロジックを常駐させるべきである。

—

4. システム間連携(API/外部DB)への発展

この「動的フィルター生成ロジック」の真価は、Excelのマクロとして完結させることではなく、外部システム(Web APIや基幹データベース)からのシグナルをトリガーにした自動制御にある。

例えば、外部のERPから「予算超過アラート」をJSONで受けて、COM経由でMS Projectをバックグラウンド起動(あるいは既存インスタンスにアタッチ)、該当するタスク群をこの動的フィルターで瞬時に抽出し、その結果(TaskオブジェクトのID配列)だけを抜き出してSQL Serverへログとして吐き出す――といったパイプラインの構築が可能だ。

レガシーと嘲笑されがちなProject VBAだが、オブジェクトモデルの挙動とメモリ管理の鉄則さえ押さえれば、いまだにデスクトップオートメーションの最強の武器となり得る。妥協のないコードで、システムを意のままに操れ。

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