Project VBAの深淵:トランザクション管理とUndoスタックの制御極意
Microsoft Project(以下、MS Project)のVBA開発において、真のシニアエンジニアと凡庸なプログラマを分かつ境界線はどこにあるか。それは単に「タスクを作れる」ことではない。「数万行のWBS構造を破壊することなく、アトミック(原子性)な処理を完遂できるか」という一点に集約される。
MS ProjectはExcelとは比較にならないほど複雑な依存関係エンジン(Scheduling Engine)を内蔵している。無計画なVBAマクロの実行は、このエンジンに過剰な再計算負荷を与え、さらにはユーザーの大切な「Undo(元に戻す)」スタックを無残に破壊する。
本稿では、大規模プロジェクトにおけるWBS自動設定において、システムを堅牢に保つためのトランザクション設計と、Windows APIを駆使した制御手法について解説する。
—
1. MS Projectにおける「Undo」の脆弱性とトランザクションの欠如
MS ProjectのVBAオブジェクトモデルには、データベースのような `Begin Transaction` や `Rollback` は存在しない。さらに厄介なことに、VBAから特定のプロパティ(特に `Task.Predecessors` や WBS構造に関わるプロパティ)を操作すると、MS Projectは内部で広範囲な再計算を行い、結果としてユーザーのUndoバッファを強制的にクリアしてしまうことが多い。
大規模な一括処理を行う際、途中でエラーが発生して処理が中断されると、プロジェクトデータは「中途半端に書き換えられた」という最悪の状態(不整合)で放置される。これを防ぐには、以下の3つのアプローチを組み合わせた「疑似トランザクション構造」を構築する必要がある。
1. State Management(状態管理): 実行前の計算モードと画面更新状態の退避。
2. Point-in-Time Recovery(一時保存による復旧): APIレベルでのバックアップ。
3. Error Isolation(エラー分離): 構造化例外処理によるロールバックの模倣。
—
2. 極限のパフォーマンスを実現するAPI制御
VBA標準の `ScreenUpdating = False` だけでは不十分な局面がある。特に数千のタスクに対して依存関係(Links)を設定する場合、MS Projectのメッセージループが介在し、描画負荷がボトルネックとなる。
ここで、Windows APIを用いてウィンドウの再描画を完全に抑制し、同時に計算エンジンを一時停止させる。
実装:システム制御クラス(CSystemManager)
‘PtrSafe宣言による64bit/32bit両対応
If VBA7 Then
Private Declare PtrSafe Function LockWindowUpdate Lib “user32” (ByVal hwndLock As LongPtr) As Long
Private Declare PtrSafe Function GetParent Lib “user32” (ByVal hWnd As LongPtr) As LongPtr
Else
Private Declare Function LockWindowUpdate Lib “user32” (ByVal hwndLock As Long) As Long
Private Declare Function GetParent Lib “user32″ (ByVal hWnd As Long) As Long
End If
Private m_originalCalcMode As Long
”’
”’
Public Sub BeginAtomicOperation()
On Error Resume Next
‘ 1. 自動計算を停止(手動計算モードへ)
m_originalCalcMode = Application.Calculation
Application.Calculation = pjManual
‘ 2. 画面更新停止
Application.ScreenUpdating = False
‘ 3. Win32 APIによる描画ロック(MS Projectのメインウィンドウを対象)
‘ 注意: LockWindowUpdateは慎重に使用すること
LockWindowUpdate Application.WindowHandle
End Sub
”’
”’
Public Sub EndAtomicOperation()
On Error Resume Next
‘ 1. APIロック解除
LockWindowUpdate 0
‘ 2. 画面更新再開
Application.ScreenUpdating = True
‘ 3. 計算モードの復元と再計算実行
Application.Calculation = m_originalCalcMode
If m_originalCalcMode = pjAutomatic Then
Application.CalculateAll
End If
End Sub
—
3. 疑似トランザクションとUndoスタックの保護
MS Projectで最も安全な「ロールバック」の手法は、処理開始直前に「非表示のバックアップ(Tempファイル)」を作成することだ。メモリ上のオブジェクト操作だけで完結させようとするのは、MS Projectの複雑なデータ構造(カレンダー、リソース、例外日)を考慮すると、リスクが高すぎる。
以下のコードは、大規模なWBS再構築を行う際の実践的なテンプレートである。
実装:トランザクション制御ロジック
Public Sub HeavyWeightWBSTransaction()
Dim archivist As New CSystemManager
Dim isSuccess As Boolean: isSuccess = False
Dim backupPath As String
‘ 1. 準備:一時的なバックアップ(ロールバック用)の作成
‘ 大規模処理前には必ずSaveCopyAsで現在の状態を物理的に保護する
backupPath = Environ(“TEMP”) & “\MSProject_Backup_” & Format(Now, “yyyymmdd_hhnnss”) & “.mpp”
ActiveProject.SaveCopyAs backupPath
archivist.BeginAtomicOperation
On Error GoTo Err_Handler
‘ — 大規模一括処理開始 —
‘ ここでタスクの生成、WBSの組み替え、依存関係の再構築を行う
‘ 例: UpdateWBSStructure()
‘ ———————–
isSuccess = True
Err_Handler:
archivist.EndAtomicOperation
If Not isSuccess Then
‘ 異常終了時の処理
Dim msg As String
msg = “致命的なエラーが発生しました。プロジェクトの一貫性を保つため、直前の状態に復元することを推奨します。” & vbCrLf & _
“Error: ” & Err.Description
MsgBox msg, vbCritical, “Transaction Failed”
‘ ロールバックの提案、または自動復旧ロジックをここに記述
‘ (手動での復旧が必要な場合、一時ファイルの場所を通知する等)
Else
‘ 正常終了時、一時ファイルを削除
On Error Resume Next
Kill backupPath
End If
‘ オブジェクトの明示的解放(メモリリーク防止)
Set archivist = Nothing
End Sub
—
4. メモリ最適化とオブジェクトのライフサイクル
Project VBAにおいて、`Task` オブジェクトや `Assignment` オブジェクトをループ内で大量に扱う場合、暗黙の参照が残り続け、メモリ消費が増大し、最終的に「Out of Memory」や「RPC Server Unavailable」を引き起こす。
これを防ぐための鉄則は、「最小スコープでのオブジェクト参照」と「Nothingによる明示的解放」である。
‘ 悪い例:数千のタスクを保持し続ける
Dim t As Task
For Each t In ActiveProject.Tasks
‘ 処理
Next t
‘ 良い例:インデックス参照を用い、ループごとに解放する
Dim i As Long
Dim currentTask As Task
For i = 1 To ActiveProject.Tasks.Count
Set currentTask = ActiveProject.Tasks(i)
If Not currentTask Is Nothing Then
‘ 依存関係の設定など、重い処理
‘ …
Set currentTask = Nothing ‘ ループ内で確実に解放
End If
Next i
特に依存関係(`Task.Dependencies`)のループ処理は重い。特定のタスクが数件以上の先行タスクを持つ場合、その解消には内部的なスケジュール再計算が走るため、可能な限り一括で(`Task.Predecessors = “1,2,3”` のように文字列で)流し込む方が、COMの往復(Round-trip)を削減でき、パフォーマンスが劇的に向上する。
—
5. アーキテクトの視点:レガシーと最先端の融合
我々が対峙しているのは、30年以上の歴史を持つMS Projectのコアエンジンである。最新の .NET 環境から操作する場合であっても、背後で動いているのはCOM(Component Object Model)の世界だ。
1. Undoスタックの「汚染」を最小限にする: ユーザーが手動で戻せる単位でマクロを分割するか、あるいは「マクロ実行後はUndo不可」であることを明示的に宣言する設計判断が必要だ。
2. 整合性チェックの自動化: トランザクションの最後には、必ず `ActiveProject.CheckOut` や特定のカスタムフィールドを用いた「整合性フラグ」の検証ルーチンを走らせるべきである。
結論
Project VBAにおけるトランザクション管理とは、単なるエラーハンドリングではない。それは、MS Projectという巨大な計算機の挙動を完全に支配し、一時的にその翼を休ませ(API制御)、外科手術(データ更新)を終えた後に、再び正しく鼓動を再開させる(再計算)一連の儀式である。
この「静と動」のコントロールを極めてこそ、真のチーフアーキテクトと呼べるだろう。現場のレガシーな資産を、この極限の知見をもって、次世代に通用する堅牢なシステムへと昇華させてほしい。
