【Project VBA極限解説】Task.Fontが描くガントチャートの真実:メモリリークを断つ動的書式制御
現場のPMOや社内システム管理者から、たびたびこのような要望が寄せられる。
「進捗率に応じて、ガントチャートのタスク名やバーの色を動的に変えたい。Excelのように直感的に視覚化したいのだと」
一見、初歩的なUI装飾の要求に見える。しかし、対象がMicrosoft Project(MSProject)であり、そのVBAオブジェクトモデルの深淵を知る者であれば、ここで安易なコードを書くことがどれほどの危険を孕んでいるか即座に察知するはずだ。
Project VBAにおける書式設定、特に `Task.Font` やグリッド、バーの動的操作は、オブジェクトのライフサイクルとCOMのメモリ管理原則を無視すれば、瞬く間にプロジェクトファイルを肥大化させ、最悪の場合はCOM例外やサイレントクラッシュを引き起こす地雷原と化す。
今回は、MSProjectのオブジェクトモデルの挙動を根本から解剖し、大規模スケジュールでも破綻しない、極限まで最適化された動的着色ロジックを提示する。
—
1. MSProjectオブジェクトモデルの暗部:なぜ `Task.Font` は危険なのか
多くの開発者は、Excel VBAのノリで以下のようなコードを書いてしまう。
‘ 【アンチパターン】絶対にやってはいけないコード
Sub BadColoring()
Dim t As Task
For Each t In ActiveProject.Tasks
If Not t Is Nothing Then
If t.PercentComplete = 100 Then
t.Font Color:=pjGreen ‘ 危険な記述
End If
End If
Next t
End Sub
このコードの何が問題か。
1. 暗黙のオブジェクト参照と解放漏れ: `For Each` ループ内で生成される `Task` オブジェクトの参照カウンタは、VBAのガベージコレクションに完全に委ねられているわけではない。特にProjectのCOMラッパーは複雑であり、ループ内での変数の使い回しや解放忘れはメモリリークの温床となる。
2. 描画スレッドの競合: MSProjectは、タスク数が数千件を超えると、個別の書式変更(`Font` メソッド等)ごとにビューの再描画(Redraw)走査が発生する。これをループ内で実行すると、UIスレッドがロックされ、最悪の場合は「応答なし」に陥る。
3. グローバル書式とローカル書式の混同: `Task.Font` は、タスクごとのローカルオーバーライドを生成する。不要なオーバーライドの乱立は、プロジェクトファイルのデータ構造(MPPフォーマット)を破損・肥大化させる原因となる。
—
2. 【極限最適化】メモリリークを根絶する動的着色アーキテクチャ
真に堅牢なProject VBAコードを書くには、以下の鉄則を遵守しなければならない。
- 画面描画の完全抑制: `Application.ScreenUpdating = False` は必須。
- オブジェクト変数の明示的スコープ管理とNothing代入: 参照を確実に断ち切り、COMコンテキストを解放する。
- 一括処理(バッチ処理)の思想: 個別プロパティの頻繁な書き換えを避け、ロジックを極限まで洗練させる。
以下に、実務の現場で即座に投入可能な、エンタープライズグレードのタスク着色エンジンを提示する。
Option Explicit
‘ ==============================================================================
‘ 模块名: MPP_VisualEngine
‘ 概要 : 進捗率に応じたタスクフォントおよびバーの動的視覚化(メモリ最適化版)
‘ 著者 : チーフアーキテクト
‘ ==============================================================================
Public Sub ApplyDynamicTaskFormatting()
‘ 描画の完全停止によるパフォーマンス爆速化
Application.ScreenUpdating = False
‘ エラーハンドリングによるCOMオブジェクトの確実な解放保証
On Error GoTo ErrorHandler
Dim t As Task
Dim tCount As Long
Dim i As Long
tCount = ActiveProject.Tasks.Count
‘ ガントチャートビューの強制アクティブ化(ビューが存在しない場合の対策)
ViewApply Name:=”Gantt Chart”
‘ For Eachを避け、インデックスアクセスでオーバーヘッドを最小化
For i = 1 To tCount
Set t = ActiveProject.Tasks(i)
‘ サマリタスクや空行(Nothing)の厳格な除外
If Not t Is Nothing Then
If Not t.Summary Then
‘ 進捗に応じたスタイリング分岐
Select Case t.PercentComplete
Case 100
‘ 完了タスク:ダークグレー・取り消し線・背景制御の例
‘ ※ Fontメソッドの引数はプロジェクトのバージョンに依存するため安全なラッパーを推奨
Call SetTaskFontSafe(t, “Meiryo UI”, 9, 8421504, True, False) ‘ 8421504 = Gray
Case Is > 0
‘ 進行中タスク:濃い青・通常
Call SetTaskFontSafe(t, “Meiryo UI”, 9, 16711680, False, False) ‘ 16711680 = Blue
Case 0
‘ 未着手タスク:黒・通常
If t.Start < ActiveProject.CurrentDate Then
' 期限超過かつ未着手は赤で警告
Call SetTaskFontSafe(t, "Meiryo UI", 9, 255, False, True) ' 255 = Red, Bold
Else
Call SetTaskFontSafe(t, "Meiryo UI", 9, 0, False, False) ' 0 = Black
End If
End Select
End If
End If
' ループ内でのオブジェクト参照破棄(メモリリーク防止の極意)
Set t = Nothing
Next i
CleanUp:
' 描画の復元と最終解放
Application.ScreenUpdating = True
Set t = Nothing
Exit Sub
ErrorHandler:
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical, "VBA Architecture Error"
Resume CleanUp
End Sub
' ------------------------------------------------------------------------------
' 内部関数: безпечный (安全な) Font設定ラッパー
' ------------------------------------------------------------------------------
Private Sub SetTaskFontSafe(ByRef targetTask As Task, _
ByVal fontName As String, _
ByVal fontSize As Long, _
ByVal fontColor As Long, _
ByVal isStrikeThrough As Boolean, _
ByVal isBold As Boolean)
On Error GoTo FontError
' Task.Font メソッドの安全な呼び出し
' 引数構成: Font(Name, Size, Color, Bold, Italic, Underline, Strikethrough, Reset)
targetTask.Font Name:=fontName, _
Size:=fontSize, _
Color:=fontColor, _
Bold:=isBold, _
Strikethrough:=isStrikeThrough
Exit Sub
FontError:
' 個別タスクの書式適用失敗は全体を止めずログ出力等に留める設計
Debug.Print "Warning: Failed to format Task ID " & targetTask.ID & " - " & Err.Description
End Sub
---
3. シニアエンジニアが知るべき「MSProject特有の罠」
上記のコードを実装するにあたり、レガシー環境や大規模プロジェクト特有の「罠」についても言及しておかねばならない。
1. MPPファイルのデータ構造破壊(Bloat)の防止
`Task.Font` を多用すると、タスクごとにローカルフォント情報が内部テーブルに蓄積され、ファイルサイズが異常肥大化する。これを防ぐため、定期的(あるいはマクロの実行前)に `FontReset` を行い、グローバルなビュー側(Text Styles)での制御に切り替える設計思想を持つことが、真のアーキテクトである。
2. 多言語環境(LCID)の考慮
`ViewApply Name:=”Gantt Chart”` の部分、もし対象のクライアント環境が英語OS上の日本語Project、あるいはその逆である場合、ビュー名文字列のハードコーディングは即座にRun-time error 1100(指定されたビューが見つかりません)を引き起こす。
実務では、ビューのID(PjBuiltInView列挙体)を用いるか、エラーフォールバックを必ず実装すべきである。
—
4. 結言
VBAにおけるUIの操作は、ともすれば「動けばいい」という妥協を生みがちだ。しかし、Microsoft Projectという巨大なスケジュールエンジンを背後に対峙するとき、メモリ、オブジェクトライフサイクル、そして描画スレッドへの敬意を欠いたコードは、必ず現場を破滅へと導く。
今回提示したコードとアーキテクチャは、単なる「タスクに色を塗るスクリプト」ではない。数千行のWBSを抱える過酷なエンタープライズ環境であっても、決して息切れしない、強靭な基盤の一部となるものである。
コードを書き飛ばすな。アーキテクチャをデザインせよ。
