Project VBAを掌握する極限の知見:動的フィルター生成による「遅延タスク」自動抽出の極意
開発現場のリーダーであるあなたなら、毎朝出社して最初にやる作業がどれほどルーティン化しているか痛感しているはずだ。
「進捗が遅れているタスクを洗い出し、ステータスを確認し、関係者にレポートを飛ばす」。
この単純作業に、何十分もの貴重なエンジニアリング時間を溶かしていないだろうか。
MS Projectの標準GUI操作によるフィルター作成は、アドホックな確認には向いていても、定常的なレポート自動化の文脈においては「百害あって一利なし」だ。ユーザーが手動でフィルター条件を触ってしまえば再現性は崩壊し、ローカライズ(日本語版・英語版)の差異によってフィールド名や比較演算子の定数が変わり、エラーの温床となる。
今回は、Project VBAのオブジェクトモデルの深淵を突き詰め、コードから動的にカスタムフィルターを生成・適用し、遅延タスクのみを完璧に屠る(抽出する)堅牢なプロダクションコードを伝授する。
—
1. なぜ「手動フィルター」や「安易なループ処理」が破綻するのか
まず、アマチュアプログラマーが陥るアンチパターンを正しておこう。
失敗パターンA:GUIで作成した固定フィルターに頼る
「あらかじめ『遅延タスク』という名前のフィルターを作っておき、VBAからそれを呼び出す」というアプローチ。一見スマートに見えるが、これでは配布先のPC環境やテンプレートの差異によって「フィルターが見つからない(エラー 1100)」という致命的な例外を引き起こす。グローバルテンプレート(Global.mpt)の汚染リスクもある。
失敗パターンB:全タスクを愚直にループして判定する
‘ 【非効率なアンチパターンの例】
Dim t As Task
For Each t In ActiveProject.Tasks
If t.PercentComplete < 100 And t.Finish < Date Then
' 処理...
End If
Next t
タスク数が数千規模に膨れ上がった瞬間、このコードは劇的なパフォーマンス低下を引き起こす。VBAとCOMオブジェクトの往復(Marshal)コストを考慮していない典型的な設計ミスだ。
模範解答:動的フィルター(FilterEdit)の採用
Project VBAでは、「実行時に一時的なフィルターをメモリ上に動的生成し、それを適用してビューを絞り込み、対象タスク群に対して一括処理を行う」のがベストプラクティスである。これにより、環境依存を排除し、MS Projectのネイティブな高速クエリエンジンをフル活用できる。
—
2. アーキテクチャ設計と実装上の罠
動的フィルターを扱う上で、シニアエンジニアが必ず押さえておかなければならない「Project特有の仕様」が2点ある。
1. FilterEditメソッドの仕様とスコープ
`FilterEdit`メソッドは、グローバルまたはプロジェクト固有のフィルターリストに新しいフィルターを追加・上書きする。ここで重要なのが、タスク用フィルター(Task:=True)かリソース用フィルター(Task:=False)かの明示、そして `ShowInMenu:=False` に設定してユーザーインターフェースを汚染しない配慮である。
2. フィールド名とローカライズの罠
VBAのフィルター条件式(Criteria)に日本語のフィールド名(例: “終了予定日”)をハードコーディングすると、英語環境やOSの言語設定差異で確実にクラッシュする。これを防ぐため、原則としてPjField定数(列挙体)を用いるか、内部的なフィールド名を参照する設計にする必要がある。
—
3. 【プロダクションコード】遅延タスク動的抽出・レポート生成エンジン
以下のコードは、実務の現場でそのまま組み込めるように設計された完全版のモジュールだ。
本日の日付を基準とし、「完了していない(% Work Complete < 100 または % Complete < 100)」かつ「終了予定日が今日より前(かつ有効なタスク)」であるものを動的フィルターで抽出し、イミディエイトウィンドウへの出力(あるいは別システム連携の土台)を行う。
Option Explicit
' ==============================================================================
' モジュール名: modDelayedTaskExtractor
' 概要 : 動的フィルターを生成し、遅延タスクを高速抽出・集計するプロフェッショナル向けモジュール
' 依存関係 : Microsoft Project 16.0 Object Library 以降
' ==============================================================================
Public Sub GenerateAndExtractDelayedTasks()
Const TEMP_FILTER_NAME As String = "_VBA_Temp_Delayed_Task_Filter"
Dim t As Task
dataValidationCheck
On Error GoTo ErrorHandler
' 1. 既存の同名一時フィルターが存在する場合は安全に削除
Call RemoveExistingFilter(TEMP_FILTER_NAME)
' 2. 動的フィルターの作成
' パラメータ:
' Name : フィルター名
' Task : True (タスク用フィルター)
' Cumulative : False
' Test1 : 条件1のフィールド (pjTaskPercentComplete)
' Operation1 : 比較演算子 (pjEqual または pjDoesNotEqual 等) -> ここでは未満を使いたいので高度なCriteriaを使用
‘ Value1 : 比較値
‘ ※FilterEditはUI依存の制限があるため、複雑な条件は Criteria パラメータを直接構築するのが堅牢
‘ 終了日が現在日付より前、かつ完了率が100%未満のタスクを抽出するCriteria式
‘ 構文: [Field] 演算子 [Value]
Dim criteriaString As String
criteriaString = “[Actual Finish] = “”NA”” AND [Finish] < Now() AND [% Complete] < 100 AND [Summary] = No"
' FilterEditによる動的フィルターの登録
' 引数の詳細: Name, Task, [Create], [Overwrite], [ShowInMenu], [Category], [And], [Field1], [Test1], [Value1], ...
FilterEdit Name:=TEMP_FILTER_NAME, _
Task:=True, _
Create:=True, _
Overwrite:=True, _
ShowInMenu:=False, _
Category:="Task", _
And:=True, _
Field1:="Finish", _
Test1:=pjTaskTestisLessThan, _
Value1:=CStr(Date), _
Operation1:=pjAnd, _
Field2:="% Complete", _
Test2:=pjTaskTestisLessThan, _
Value2:="100", _
Operation2:=pjAnd, _
Field3:="Summary", _
Test3:=pjTaskTestEquals, _
Value3:="No"
' 3. フィルターの適用
' ApplyFilter メソッドで作成したフィルターをアクティブビューに適用
ViewApply Name:="&Gantt Chart" ' 標準ガントチャートビューを強制
FilterApply Name:=TEMP_FILTER_NAME
' 4. 抽出されたタスク群に対する高効率なバッチ処理(ループ)
' フィルターが適用されているため、ActiveProject.Tasksの走査も遅延タスクのみに絞られる
Dim delayedCount As Long
delayedCount = 0
Debug.Print "=== 遅延タスク抽出レポート (実行日時: " & Now() & " === "
For Each t In ActiveProject.Tasks
' Nothing判定とサマリータスク(念のため)の除外
If Not t Is Nothing Then
If Not t.Summary Then
delayedCount = delayedCount + 1
Debug.Print "ID: " & t.ID & " | 名称: " & t.Name & " | 担当者: " & GetResourceNames(t) & " | 終了予定: " & t.Finish & " | 進捗率: " & t.PercentComplete & "%"
End If
End If
Next t
Debug.Print "=== 抽出完了: 合計 " & delayedCount & " 件の遅延タスクを検出しました。 ==="
' 5. クリーンアップ(フィルターの解除と一時フィルターの削除)
FilterClear
Call RemoveExistingFilter(TEMP_FILTER_NAME)
MsgBox "遅延タスクの抽出が完了しました。検出件数: " & delayedCount & "件", vbInformation, "処理成功"
Exit Sub
ErrorHandler:
' 異常系ハンドリング
MsgBox "予期せぬエラーが発生しました。" & vbCrLf & _
"Error No: " & Err.Number & vbCrLf & _
"Description: " & Err.Description, vbCritical, "致命的エラー"
' 障害時のクリーンアップ
On Error Resume Next
FilterClear
Call RemoveExistingFilter(TEMP_FILTER_NAME)
End Sub
' ==============================================================================
' 補助プロシージャ: 既存フィルターの安全な削除
' ==============================================================================
Private Sub RemoveExistingFilter(ByVal filterName As String)
Dim f As Filter
On Error Resume Next
For Each f In ActiveProject.Filters
If f.Name = filterName Then
f.Delete
Exit For
End If
Next f
On Error GoTo 0
End Sub
' ==============================================================================
' 補助プロシージャ: タスクに割り当てられたリソース名の一括取得
' ==============================================================================
Private Function GetResourceNames(ByRef t As Task) As String
Dim r As Resource
Dim names As String
names = ""
On Error Resume Next
For Each r In t.Resources
If names = "" Then
names = r.Name
Else
names = names & ", " & r.Name
End If
Next r
On Error GoTo 0
If names = "" Then names = "未割当"
GetResourceNames = names
End Function
' ==============================================================================
' 前提条件バリデーション
' ==============================================================================
Private Sub dataValidationCheck()
If ActiveProject Is Nothing Then
Err.Raise 9999, "Validation", "有効なプロジェクトが開かれていません。"
End If
End Sub
---
4. コードの深層解説:なぜこの設計が「プロフェッショナル」なのか
1. 一時フィルターのクリーンナップ保証(Garbage Collectionの意識)
VBAで動的オブジェクト(ファイル、フィルター、一時テーブルなど)を生成する場合、例外が発生して処理が中断されると、ゴミが残る。このコードでは `ErrorHandler` ラベル内に確実にクリーンアップ処理(`FilterClear` と `RemoveExistingFilter`)を配置し、プロジェクトファイルを汚染しない設計にしている。
2. サマリータスク(親タスク)の厳格な除外
MS Projectにおいて、サマリータスクの進捗や日時は子タスクから自動計算されるため、サマリータスク自体を「遅延タクス」として担当者に督促するのはマネジメント上の大チョンボだ。`[Summary] = No`(コード上では `t.Summary` の判定)を挟むことで、実務で本当に追うべき末端(リーフ)タスクだけを正確に射抜く。
3. リソースアサイン情報の安全な取得
`t.Resources` コレクションを走査する際、リソースが未割当のタスクが存在すると予期せぬランタイムエラーを誘発する。`On Error Resume Next` を極小のスコープで囲み、安全にフォールバック(”未割当”文字列の返却)させる防衛的プログラミングを徹底している。
—
5. さらなる高みへ:ファイル・データベース連携への拡張
この動的フィルターで抽出した遅延タスクのデータ構造は、そのまま外部データベース(SQL Server / Azure SQL)や、Excelダッシュボード、さらにはMicrosoft Teams / SlackのWebhook APIへのJSONペイロードへと流し込むことが可能だ。
「朝出社した瞬間に、前日の遅延タスク一覧が自動でTeamsのPMOチャンネルに飛んでいる」。
Project VBAを極めたあなたなら、このコードを基点に、組織全体のプロジェクトマネジメントコストを劇的にゼロへと近づけられるはずだ。
手動オペレーションという名の技術的負債を、今日、このコードですべて排除せよ。
