Project VBAの深淵:ビュー制御とフィルタリングの「動的最適化」を極める
多くのエンジニアがMicrosoft ProjectのVBAを「単なる記録ツール」だと誤解している。しかし、Projectオブジェクトモデルは、Excelとは比較にならないほど複雑なイベント駆動と再計算処理の迷宮だ。
特に、現場から突きつけられる「特定の報告用ビューへの自動切り替えとフィルタリング」という要件。これを安易に `ViewApply` メソッドを叩くだけで実装しているようでは、大規模プロジェクトにおいてメモリリークや再計算ループという名の自爆テロを招くことになる。
今回は、シニアエンジニアが知るべき「制御の極致」を伝授する。
—
1. ビュー切り替えの「定石」と「裏側」
`ViewApply` は便利だが、Projectの内部状態を不安定にさせるトリガーでもある。特に「フィルタ」を同時に適用する場合、適用順序を誤れば再計算(リカルク)が二重に走り、パフォーマンスを劇的に劣化させる。
最も安全な方法は、`ScreenUpdating` を制御し、オブジェクトの参照を明示的にクリアすることだ。
‘ ビューとフィルタを安全に切り替えるためのラッパー
Public Sub ApplyViewAndFilter(ByVal viewName As String, Optional ByVal filterName As String)
Dim projApp As Object
Set projApp = Application
‘ 画面描画を停止し、再計算のオーバーヘッドを抑える
projApp.ScreenUpdating = False
On Error GoTo Cleanup
‘ ビューの適用。引数に詳細な定数を渡すことが重要
projApp.ViewApply Name:=viewName
‘ フィルタがある場合は適用。Falseは既存のフィルタを置換する意
If Len(filterName) > 0 Then
projApp.FilterApply Name:=filterName, SelectedTasks:=False
End If
Cleanup:
‘ 描画を復旧。ここを怠るとユーザーはフリーズしたと勘違いする
projApp.ScreenUpdating = True
Set projApp = Nothing
End Sub
2. オブジェクトライフサイクルの管理:メモリリークを根絶する
Project VBAにおいて、`ActiveProject` や `ActiveSelection` を安易に使い回してはならない。これらは常に動的なポインタであり、ガベージコレクションの挙動を予測不可能にする。
特に、大規模なスケジュール(数千タスク規模)を扱う際、ループ内で `Task` オブジェクトを解放せず放置すると、メモリ消費量は右肩上がりになる。
極意: 参照型オブジェクトは必ず `Nothing` で明示的に解放し、インデックスアクセスを優先せよ。
Public Sub OptimizeTaskLoop()
Dim tsk As Task
Dim proj As Project
Set proj = ActiveProject
‘ タスクをコレクションから抽出する際は、For Eachではなく
‘ インデックスアクセスがメモリ効率の面で有利な場合が多い
Dim i As Long
For i = 1 To proj.Tasks.Count
Set tsk = proj.Tasks(i)
If Not tsk Is Nothing Then
‘ ここで処理
‘ tsk.Name = …
End If
‘ 重要なステップ:明示的な解放
Set tsk = Nothing
Next i
Set proj = Nothing
End Sub
3. Windows APIによる「異常系」のハンドリング
Projectが「計算中」の状態に入ると、VBAの実行がブロックされることがある。これを力技で解決するのが、Windows APIによるメッセージキューの空打ちだ。
もしプロジェクトファイルが重すぎて、ビュー切り替え後に応答なしになる場合は、以下の `DoEvents` を拡張した処理を挟むのが定石である。
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If
‘ 再計算の完了を待機するための安全装置
Public Sub WaitUntilCalculated()
‘ 計算処理が完全に終わるまで待機するアーキテクチャ
Do While Application.IsCalculating
DoEvents
Sleep 100 ‘ CPU負荷を下げつつイベントを処理
Loop
End Sub
4. シニアアーキテクトからの提言:レガシーとの共存
Project VBAは20年以上、本質的な構造を変えていない。これは「保守性が極めて高い」ことを意味するが、同時に「現代的な非同期処理には向かない」ことも意味する。
1. フィルタの動的生成: `FilterEdit` を使って実行時にフィルタを生成し、終わったら `FilterDelete` で掃除せよ。残骸を残すシステムは、やがて腐敗する。
2. システム間連携: プロジェクト情報を外部DBやREST APIへ投げる際は、必ずJSON変換層を挟め。直接Projectの内部オブジェクトを外部とバインドするのは、セキュリティ的にも構造的にも自殺行為だ。
結び
Projectの自動化において、コードの「量」は悪だ。必要なオブジェクトを最小限のスコープで生成し、処理が完了次第、速やかにメモリから消し去る。この「潔さ」こそが、数千行のコードを抱えるシステムを安定させる唯一の鍵である。
君たちが書くコードが、明日のプロジェクトマネージャーの工数を削る武器となることを期待している。
