Microsoft ProjectのVBA(MS Project VBA)は、Excel VBAの延長線上で捉えると必ず手痛い火傷を負う。特に数千行を超えるタスク(WBS)の自動生成や、複雑な依存関係(Predecessors)のバッチ処理を行う際、初心者が陥るのが「Undo(元に戻す)スタックの枯渇」と「不整合なデータの残留」だ。
プロジェクトマネジメントツールにおいて、不整合なデータはスケジュールの崩壊、ひいてはプロジェクトの死を意味する。
今回は、Project VBAを真に掌握するために不可欠な、トランザクション的設計思想とUndo管理の極意を伝授する。
—
1. なぜProject VBAに「トランザクション」が必要なのか
MS Projectは、Excelと異なり、一つのタスクの変更がプロジェクト全体(スケジュール、コスト、リソース割り当て)に即座に波及する「計算エンジン」の塊だ。
1,000個のタスクに依存関係を設定する際、何も考えずにループを回せば、MS Projectは1回ごとに再計算を行い、1回ごとにUndoスタックに履歴を書き込む。これは以下の致命的な問題を引き起こす。
1. パフォーマンスの指数関数的な低下: 再計算のオーバーヘッドが積み重なる。
2. Undoスタックの断片化: エラー発生時、ユーザーが「Ctrl+Z」を何度押しても「中途半端に壊れた状態」までしか戻せない。
3. 不整合の発生: 途中でエラーが起きた際、一部のタスクだけがリンクされ、残りが浮いている状態になる。
我々プロフェッショナルが目指すべきは、「全件成功して確定するか、失敗して完全に作業前の状態に戻るか」の二択――すなわちトランザクション管理の実装だ。
—
2. Undoスタックを制御する「グループ化」の秘策
残念ながら、Project VBAにはSQLのような `Begin Transaction` 命令は存在しない。しかし、MS Projectには `App.Undo` という強力な武器がある。
大規模処理を行う際は、以下の3つのレイヤーで防御を固めるのが定石だ。
① 計算エンジンと画面更新の停止
処理の開始前に、MS Projectの動的な挙動を封じ込める。これだけで処理速度は数倍から数十倍に跳ね上がる。
② Undo履歴の「強制リセット」または「マーク」
処理開始直前に一旦保存するか、あるいはエラー発生時に「どこまで戻すべきか」を特定するためのステート(状態)を保持する。
③ エラーハンドリングによるロールバック
VBAの `On Error GoTo` を単なるエラー回避ではなく、「プロジェクトの状態を復元するためのトリガー」として機能させる。
—
3. 実戦用プロダクションコード:堅牢なWBS構築エンジン
以下のコードは、外部データ(CSVやDBを想定)からタスク構造と依存関係をインポートする際の、理想的なテンプレートである。
Option Explicit
”’
”’
Public Sub RobustWBSImport()
Dim appProject As MSProject.Application
Set appProject = MSProject.Application
‘ 1. 実行前のプロジェクト状態を保持(ロールバック用)
‘ 大規模処理の場合、一時ファイルに保存するか、Undo履歴を意識する
Dim isCalculationManual As Boolean
isCalculationManual = (appProject.Calculation = pjManual)
On Error GoTo ErrorHandler
‘ 2. パフォーマンス最適化:画面更新と自動計算の停止
appProject.ScreenUpdating = False
appProject.Calculation = pjManual
‘ — トランザクション・ブロック開始 —
Debug.Print “処理開始: ” & Now
‘ タスク生成ロジックの呼び出し
‘ ※ここでは例として100個のタスクと依存関係を生成
Call CreateTaskHierarchyAndLinks(appProject)
‘ — トランザクション・ブロック終了 —
‘ 3. 正常終了時の処理:計算を再開し、変更を確定させる
appProject.Calculation = pjAutomatic
appProject.ScreenUpdating = True
MsgBox “WBSの構築が正常に完了しました。”, vbInformation
Exit Sub
ErrorHandler:
‘ 4. ロールバック処理
‘ エラーが発生した場合、画面更新を戻し、ユーザーに通知
appProject.ScreenUpdating = True
appProject.Calculation = IIf(isCalculationManual, pjManual, pjAutomatic)
Dim errMsg As String
errMsg = “エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description & vbCrLf & vbCrLf & _
“プロジェクトの整合性を保つため、処理を中断しました。”
‘ 重大なエラーの場合、Undoを実行して作業前の状態に戻す
‘ ※MS ProjectのUndoは1ステップずつしか戻せないため、
‘ 確実に元に戻すには「保存せずに閉じる」か「バックアップからの復旧」も検討する
If appProject.CanUndo Then
appProject.Undo
End If
MsgBox errMsg, vbCritical, “トランザクションエラー”
End Sub
Private Sub CreateTaskHierarchyAndLinks(ByRef appProj As MSProject.Application)
Dim activeProj As Project
Set activeProj = appProj.ActiveProject
Dim i As Long
Dim tsk As Task
Dim prevTsk As Task
‘ 高速化の極意: Taskオブジェクトへの参照を最小限にし、
‘ プロパティへの一括アクセスを意識する
For i = 1 To 100
‘ タスクの生成
Set tsk = activeProj.Tasks.Add(Name:=”自動生成タスク_” & i)
‘ WBS階層の設定(例:5タスクごとにアウトデント)
If i Mod 5 = 0 Then
tsk.OutlineLevel = 2
Else
tsk.OutlineLevel = 1
End If
‘ 依存関係の設定(Predecessors)
‘ ※ここで不正な文字列(ループ参照など)を渡すとProjectは即座にエラーを吐く
‘ 事前にロジック側で循環参照をチェックしておくのがプロの仕事だ
If Not prevTsk Is Nothing Then
‘ リンク設定の際は、ID指定ではなく、直接Linkメソッドを叩くのが安全
tsk.LinkPredecessors Tasks:=prevTsk, LinkType:=pjFinishToStart
End If
Set prevTsk = tsk
Next i
End Sub
—
4. 現場で差が出る「保守性」と「堅牢性」のポイント
循環参照の事前検知
Project VBAで最も恐ろしいのは、依存関係の設定ミスによる「循環参照エラー」だ。これはVBA実行中にポップアップを出し、スクリプトの実行を停止させる。
外部DBからリンク情報を引っ張る際は、VBA内部でタスクIDの親子関係を `Dictionary` オブジェクト等に保持し、リンクを張る前に「自分自身や自分の先祖を先行タスクに指定していないか」を論理チェックする層を必ず挟め。
ファイル・データベース連携の注意点
外部のSQL ServerやExcelからデータを取得する場合、`ADODB.Connection` は最短時間でクローズせよ。Projectの処理(特に再計算)は重いため、DB接続を維持したままProjectのオブジェクト操作を行うと、DB側のタイムアウトやデッドロックを誘発する恐れがある。
「データを全てメモリ(配列やコレクション)に読み込む」→「DBを閉じる」→「Projectを操作する」の3ステップを徹底すること。
Undoスタックの限界
MS ProjectのUndoスタックには上限がある。数万行の操作を行う場合、Undo履歴がメモリを圧迫し、動作が著しく不安定になる。
このような超大規模処理では、あえて `appProject.Undo` に頼らず、「処理開始前にプロジェクトファイルを一時的な別名で保存する(Shadow Copy)」手法を採用すべきだ。エラーが起きたら現在のファイルを破棄し、バックアップを開き直す。これが最も確実なロールバックである。
—
結論:美しさは堅牢さの中に宿る
Project VBAのコーディングにおいて、単に「動く」だけのコードは素人の仕事だ。
予期せぬエラー、不正な入力データ、ユーザーによる中断……。あらゆるノイズを想定し、「プロジェクトという資産を絶対に壊さない」という意志をコードに込めること。
トランザクション管理とUndo制御をマスターすれば、あなたの作成するツールは単なる自動化を超え、エンタープライズレベルの信頼を得ることになるだろう。
掌握せよ。Projectの計算エンジンを、そしてその背後にあるデータの整合性を。
