【Project VBA極限知見】OutlineLevelを制す者、WBSの宇宙を制す —— タスク階層制御の深層
シニアエンジニア、そして企業インフラの深淵を支えるシステム管理者諸君。
日々のプロジェクト管理において、数千行に及ぶWBS(Work Breakdown Structure)のインデント調整に絶望したことはないだろうか。GUIのマウス操作やショートカットキーによる手動インデントは、小規模なスケジュールであれば機能するが、基幹システムや外部連携APIから生成されたフラットなタスク群を動的に体系化する現場においては、完全に無力である。
Project VBA(MS ProjectのVBA環境)におけるタスクの階層構造操作は、単なる「見た目の整理」ではない。WBSの論理構造を定義し、Earned Value Management(EVM)の計算根拠を決定づける極めてクリティカルな操作なのだ。
今回は、初心者向けと銘打ちつつも、プロフェッショナルの現場で生き抜くための`OutlineLevel`プロパティの本質と、メモリ・パフォーマンスを極限までチューニングしたタスク階層自動化の実装パターンを伝授する。
—
1. OutlineLevelプロパティの正体:見た目ではなく「論理構造」の制御
MS Projectのオブジェクトモデルにおいて、タスク(`Task`オブジェクト)のインデントを操作する方法はいくつか存在する。しかし、GUIの「インデント」「アウトデント」ボタンに依存するようなコードは、レガシーな悪習として直ちに排除せよ。
我々が操作すべきは、`Task.OutlineLevel` プロパティである。
‘ タスクの階層レベルを直接指定する例
t.OutlineLevel = 2
【チーフアーキテクトの警鐘】GUIメソッドの排除
`App.OutlineIndent` や `App.OutlineOutdent` といったApplicationレベルのメソッドは、現在選択されているビュー(ActivePane)のコンテキストに強く依存する。
つまり、バックグラウンド処理や、ビューが切り替わっている予期せぬタイミングでこれらを呼び出すと、ランダムな位置のタスクが誤インデントされる致命的なサイレントバグを引き起こす。
タスクの階層化は、必ず対象の `Task` オブジェクトを特定し、`OutlineLevel` プロパティへ直接数値を代入して完結させなければならない。これが堅牢なシステム間連携における大原則である。
—
2. 【実践】フラットデータを構造化WBSへ昇華させるVBAコード
外部CSVやデータベースから取り込んだ「親子の関係がIDで表現されたフラットなタスク群」を、Project内で正確に階層化する実用コードを提示する。
オブジェクトのライフサイクル管理、パフォーマンス最適化のイディオムを完全に網羅している。
Option Explicit
”’
”’
Sub BuildWBSHierarchy()
‘ パフォーマンス最大化の定石:画面描画と自動再計算の完全停止
Dim originalScreenUpdating As Boolean
Dim originalCalculation As Long
originalScreenUpdating = Not Application.ScreenUpdating
Application.ScreenUpdating = False
originalCalculation = Application.Calculation
Application.Calculation = pjCalculationManual ‘ 手動計算モードへ移行
On Error GoTo ErrorHandler
Dim tsk As Task
Dim proj As Project
Set proj = ActiveProject
‘ 【重要】MS Projectでは、タスクの操作は必ずIDの「降順(下から上)」で行うか、
‘ 親から子へと正確に制御する必要がある。今回は一括走査のサンプル。
For Each tsk in proj.Tasks
If Not tsk Is Nothing Then
‘ 例:タスク名に特定のプレフィックスがある場合、階層を深くする(昇格・降格のロジック)
If Left(tsk.Name, 3) = ” ” Then
tsk.OutlineLevel = 2
ElseIf Left(tsk.Name, 6) = ” ” Then
tsk.OutlineLevel = 3
Else
tsk.OutlineLevel = 1
End If
End If
For Each tsk In proj.Tasks
‘ Nothing判定(ProjectのTasksコレクション特有の穴あき対策)
If Not tsk Is Nothing Then
‘ ビジネスロジックに応じたOutlineLevelの設定
‘ ※実際の現場では、カスタムフィールドやWBSコードの桁数からレベルを算出して代入する
Select Case tsk.Text1 ‘ 例: Text1に階層コードが入っていると仮定
Case “1”
tsk.OutlineLevel = 1
Case “1.1”, “1.2”
tsk.OutlineLevel = 2
Case “1.1.1”, “1.1.2”
tsk.OutlineLevel = 3
Case Else
‘ デフォルトは最上位
If tsk.OutlineLevel = 0 Then tsk.OutlineLevel = 1
End Select
End If
Next tsk
‘ 変更を反映するために強制再計算
Application.CalculateAll
CleanUp:
‘ 状態の復元(例外発生時も確実に実行する)
Application.ScreenUpdating = True
Application.Calculation = originalCalculation
‘ オブジェクトの明示的解放(メモリリーク防止)
Set tsk = Nothing
Set proj = Nothing
Exit Sub
ErrorHandler:
MsgBox “WBS構築中に致命的なエラーが発生しました: ” & Err.Description, vbCritical, “System Error”
Resume CleanUp
End Sub
—
3. 極限の知見:パフォーマンスとメモリ最適化の真実
大規模なプロジェクト(タスク数10,000件超)において、上記のような一括処理を行う際、初心者が陥る罠がある。それらを回避するためのアーキテクトの知見を授けよう。
① `ScreenUpdating` と `Calculation` の封印
MS Projectは、1つのタスクの `OutlineLevel` が変更されるたびに、サマリータスク(要約タスク)の期間再計算、コスト集計、WBSコードの振り直し、そしてGUIの再描画を裏側で実行する。
数千件のタスクに対してこれを許容すれば、処理は数分から数十分でフリーズするだろう。
コード例の通り、処理の冒頭で `Application.ScreenUpdating = False` と `Application.Calculation = pjCalculationManual` を適用し、全ての構造変更が終わった後に一度だけ `CalculateAll` を叩く。これが秒速処理を実現する唯一の解である。
② Project VBA特有の「Nothing」トラップ
Excelの `Range` や `Worksheet` と異なり、MS Projectの `Tasks` コレクションや `Resources` コレクションは、タスクが削除された際にIDの欠番が生じ、`For Each` ループ内で `Nothing` を返すケースがある。
これを防ぐための `If Not tsk Is Nothing Then` のガード句を省く者は、必ず「オブジェクト変数または With ブロック変数が設定されていません。(Error 91)」の洗礼を受けることになる。
③ COMオブジェクトの参照解放
VBAはガベージコレクションが曖昧である。プロシージャの終了時に `Set tsk = Nothing` を記述するのは基本中の基本だが、特にCOM相互運用(VB.NETやC#からのCOM呼び出し)を視野に入れている場合、Projectインスタンスの参照がメモリ上に残留し、バックグラウンドプロセス(`WINPROJ.EXE`)がゾンビ化する原因となる。
ローカル変数は必ず明示的に解放せよ。
—
総括
タスクの階層構造(WBS)の自動化は、単なるコードの記述量を超えた「プロジェクトの論理構造そのものをコード化する行為」である。
`OutlineLevel` プロパティの本質を理解し、GUI依存の脆弱なコードを排し、パフォーマンスチューニングの鉄則を守ることで、あなたの書くVBAスクリプトは、単なる「マクロ」から「エンタープライズ・オーケストレーション・ツール」へと昇華する。
極限まで最適化されたコードで、重厚長大なWBSの宇宙を意のままに操り給え。
