【テクニカル・上級編】TaskのカスタムフィールドをVBAで一括更新する:大量の進捗管理データを効率的に流し込む方法 – Project VBA解析バイブル

スポンサーリンク

MS Project VBAの極限:カスタムフィールド一括更新におけるパフォーマンスの壁と最適解

シニアアーキテクト諸君。日々のプロジェクトマネジメントにおいて、Excelや外部基幹システムから吐き出された数千行に及ぶ進捗・コストデータを、MS Projectのタスクカスタムフィールド(Text1〜30, Number1〜20など)へ流し込む作業に絶望したことはないだろうか。

愚直に `ActiveProject.Tasks` を上から順にループさせ、セルごとに値を代入していくコードを書けば、数千件のタスクで数分から場合によっては数十分のフリーズ地獄が待っている。進捗率の更新ごときにコーヒーブレイクなど不要だ。

今回は、MS Project VBAのオブジェクトモデルの挙動、COMコンポーネントのライフサイクル、そして画面描画のオーバーヘッドを完全に支配し、「一瞬で」数万件のカスタムフィールドを同期させる極限のテクニックを授ける。

1. なぜMS Project VBAは遅いのか?(根本原因の解剖)

MS ProjectのCOMオブジェクトモデルを操作する際、開発者が陥る最大の罠は「UIの自動再描画(ScreenUpdating)」と「計算エンジン(Calculation)の都度実行」である。

デフォルトの状態では、VBAから `Task.Text1 = “Value”` を1回実行するたびに、以下の重処理が内部で走る。
1. タスクデータのメモリ上での書き換え
2. 依存関係(WBS、サマリータスク、クリティカルパス)の再計算
3. ガントチャートやリソースビューの画面描画の強制更新

これを数千回繰り返せば、VBAとMS Projectの間でCOMのMarshalling(マーシャリング)が頻発し、CPUは無駄な描画と再計算にリソースを食いつぶされる。

2. 現場で使える極限最適化コード:一括更新エンジン

以下のコードは、画面描画の停止、計算の手動化、そしてオブジェクトの適切な参照管理を網羅した、実戦投入仕様のファンクションだ。外部のCSVや別システムからのデータを一気に流し込む想定で構築している。

Option Explicit

‘ Windows API: 処理中の画面フリーズを防ぎつつ、OSに制御を返すための最低限の宣言(必要に応じて使用)
If VBA7 Then
Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If

Public Sub ExecuteHighSpeedTaskCustomFieldUpdate()
Dim startTime As Double
startTime = Timer

‘ —————————————————-
‘ 1. 環境の事前最適化(パフォーマンスの神髄)
‘ —————————————————-
Dim originalCalcMode As Long
originalCalcMode = Application.Calculation

‘ 計算モードを手動に切り替え、データ流し込み中の無駄な再計算を完全に殺す
Application.Calculation = pjCalculationManual

‘ 画面描画を停止(これだけで速度が桁違いになる)
AppActivate ActiveProject.Name
Application.ScreenUpdating = False

‘ エラーハンドリングの要(途中で落ちても環境を必ず復元するため)
On Error GoTo ErrorHandler

‘ —————————————————-
‘ 2. データ構造の準備(本来はここで外部DBやDictionaryから取得)
‘ —————————————————-
‘ キー: タスクID (UniqueID), 値: セットする文字列(例としてText1とNumber1を想定)

Dim targetProject As Project
Set targetProject = ActiveProject

Dim tsk As Task
Dim updateCount As Long
updateCount = 0

‘ —————————————————-
‘ 3. 高速ループ処理(オブジェクトのスコープ最適化)
‘ —————————————————-
‘ ※注意: ActiveProject.Tasks.Count をループの終端に直接置くのは遅延の元。あらかじめ取得する。
Dim totalTasks As Long
totalTasks = targetProject.Tasks.Count

Dim i As Long
For i = 1 To totalTasks
‘ Nothingのタスク(削除済みや空行)をスキップ
Set tsk = targetProject.Tasks(i)
If Not tsk Is Nothing Then
‘ サマリータスクを除外する場合などの条件を入れると堅牢
If Not tsk.Summary Then

‘ 【ここに実際の更新ロジックを記述】
‘ 例:外字や特殊文字のエスケープ、型変換をここで行う
tsk.Text1 = “Synced_” & tsk.UniqueID
tsk.Number1 = tsk.UniqueID 10

updateCount = updateCount + 1
End If

‘ メモリリーク防護のためループごとの参照破棄
Set tsk = Nothing
End If

‘ 1000件ごとにシステムへ制御を返す(長大プロジェクトでのVBAクラッシュ防止)
If i Mod 1000 = 0 Then
DoEvents
End If
Next i

‘ —————————————————-
DictionaryCleanUp:
‘ —————————————————-
‘ 4. 環境の復元と再計算の実行
Application.ScreenUpdating = True
Application.Calculation = originalCalcMode

‘ 保留されていた変更をまとめて再計算
If originalCalcMode = pjCalculationAutomatic Then
targetProject.Calculate
End If

MsgBox “カスタムフィールドの一括更新が完了しました。” & vbCrLf & _
“処理件数: ” & updateCount & ” 件” & vbCrLf & _
“処理時間: ” & Format(Timer – startTime, “0.00”) & ” 秒”, vbInformation, “最適化完了”
Exit Sub

ErrorHandler:
‘ 異常終了時も必ず画面と計算モードを戻す(戻さないとProjectが使い物にならなくなる)
Application.ScreenUpdating = True
Application.Calculation = originalCalcMode

MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “VBA実行エラー”
If Not tsk Is Nothing Then Set tsk = Nothing
End Sub

3. チーフアーキテクトが教える実装上の急所(知見の共有)

上記のコードを実務の巨大プロジェクトファイル(数万行、数レプリカのWBS)に適用する際、以下のポイントを遵守してほしい。

① `UniqueID` と `ID` の違いを絶対に混同するな

MS Projectには、行の並び順によって変動する `Task.ID` と、一度発行されたら二度と変わらない永続的な識別子 `Task.UniqueID` が存在する。
外部システム(Excelなど)とデータを突き合わせてカスタムフィールドを更新する場合、絶対に `UniqueID` をキーに使うこと。 `ID` を使うと、ユーザーがタスクを1行挿入・移動しただけで、全く別のタスクにデータが上書きされる大惨事を引き起こす。

② `Calculation = pjCalculationManual` の強制と副作用

大規模なプロジェクトで手動計算モードに切り替える最大のメリットは「依存関係の連鎖的な再計算コストの排除」だが、反面、コード実行後に `Project.Calculate` を叩き忘れると、画面上の工期やコストの数値が古いままになり、ユーザーが混乱する。
必ずエラーハンドリング(`On Error GoTo`)のブロック内でも環境変数の復元(ScreenUpdatingとCalculationの戻し)を保証する構造にしなければならない。

③ COMオブジェクトの解放(メモリ管理)

VBAはガベージコレクションが非同期かつ曖昧な言語である。特にMS Projectの `Tasks` コレクションはメモリ食い虫だ。ループ内で `Set tsk = targetProject.Tasks(i)` とイテレートしたら、ループの末尾で必ず `Set tsk = Nothing` を明示的に行い、COMラッパーの参照カウンタを即座にデクリメントすること。これをサボると、処理終盤で「メモリ不足」によるVBAランタイムエラーが誘発される。

最後に:レガシーの先にあるもの

VBAはレガシーな技術と揶揄されることもあるが、MS Projectのローカル環境、あるいは閉じたネットワーク内のエンタープライズ環境において、これほど手軽に、かつ深くプロジェクト構造をハックできるインターフェースは他に存在しない。

数分かかっていた手作業や非効率なマクロを、数秒の「洗練されたアルゴリズム」に置き換えることこそ、プロフェッショナルなエンジニアの矜持である。今日のコードをあなたのプロジェクト管理基盤に組み込み、圧倒的なパフォーマンスを体感してほしい。

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