スケジュール計算エンジンを支配せよ:MS Project VBAにおける制約条件の精密制御
MS Projectを単なる「高機能なガントチャート・エクセル」だと思っているなら、君の自動化ツールは必ず現場で破綻する。
MS Projectの本質は、入力された日付や制約条件を基に、依存関係(Predecessors)を辿って全タスクの開始・終了日を再計算する「動的なスケジュール計算エンジン」である。
多くの初学者が書くVBAコードが、実行時に数秒、あるいは数分間フリーズするのは、プログラムの書き方が悪いのではない。「計算エンジンの再トリガー(再計算)」を制御できていないからだ。
本記事では、プロジェクト作成時に自動付与される「制約なし(As Soon As Possible)」の状態を検出し、実務で求められる特定のデフォルト値へと一括置換する、堅牢かつ高速な実装手法を伝授する。
1. なぜ「制約(Constraint)」の制御が重要なのか
MS Projectにおいて、タスクには必ず「制約」が付随する。
何も指定しない場合、タスクはデフォルトで `pjTaskConstraintAsSoonAsPossible`(可能な限り早く) という制約を持つ。これは一見「制約がない」ように見えるが、計算エンジンにとっては「ASAPという制約がある」状態なのだ。
実務上の自動化ツールでは、以下の理由からこれを明示的に制御する必要がある。
1. スケジュールの不整合防止: 意図しないASAPが残っていると、先行タスクが動いた際にタスクが連鎖的に動きすぎてしまい、計画の安定性が失われる。
2. 標準化: プロジェクト管理基準に基づき、「開始日指定(Start No Earlier Than)」などの特定の制約へ強制的に統一する必要がある。
3. データクレンジング: 外部システム(ERPや他の工程管理ツール)からインポートした際、不適切な制約が混入しているケースが多いため。
2. 初学者が陥る「致命的な設計ミス」
コードを書く前に、避けるべき「アンチパターン」を理解しておけ。
アンチパターン:逐次計算の地獄
' --- 悪い例:絶対にやってはいけない ---
For Each t In ActiveProject.Tasks
If Not t Is Nothing Then
If t.ConstraintType = pjTaskConstraintAsSoonAsPossible Then
t.ConstraintType = pjTaskConstraintStartNoEarlierThan
' ここで、タスクを1つ書き換えるたびに、
' MS Projectの計算エンジンがプロジェクト全体の再計算を開始する!
End If
End If
Next t
この書き方では、タスクが1,000個あれば、最悪1,000回のフルスケジューリングが走る。計算コストの爆発だ。プロフェッショナルなら、まず計算モードを「手動」に切り替えるのが鉄則だ。
3. プロダクション・グレードの設計指針
堅牢なツールを作るための3つの柱を提示する。
1. 計算制御(Calculation Control): 処理の開始前に `Calculation = pjCalculationManual` を実行し、終了後に `Automatic` に戻す。
2. Nullオブジェクトへの耐性: `Tasks` コレクションには、空行(Nothing)が含まれる。これを無視すると `Run-time error ’91’` で即死する。
3. エラーハンドリングと状態復元: 途中でエラーが発生しても、計算モードを必ず `Automatic` に戻す仕組み(クリーンアップ処理)を組み込む。
4. 実装:制約条件の最適化エンジン
以下に、実務でそのまま使えるレベルのコードを示す。このコードは、全ての「ASAP」制約を検出し、指定したデフォルトの制約(例:開始日指定)へ置換する。
Option Explicit
''' <summary>
''' プロジェクト内の全タスクを走査し、"可能な限り早く(ASAP)"の制約を
''' 指定されたデフォルトの制約タイプに一括置換する。
''' </summary>
Public Sub StandardizeTaskConstraints()
Dim t As Task
Dim targetConstraint As PjTaskConstraint
Dim processedCount As Long
' --- 設定領域 ---
' 置換後の制約タイプを指定(例: pjTaskConstraintStartNoEarlierThan = 開始日指定)
targetConstraint = pjTaskConstraintStartNoEarlierThan
' ----------------
' 1. 高速化のための準備
On Error GoTo ErrorHandler
Application.ScreenUpdating = False
' 計算モードを手動に設定(再計算の連鎖を阻止)
Application.Calculation = pjCalculationManual
processedCount = 0
' 2. ループ処理
' ActiveProject.Tasks を直接回す際は、Nothingチェックが必須
For Each t In ActiveProject.Tasks
' タスクが存在しない(空行)場合はスキップ
If Not t Is Nothing Then
' 制約タイプが "As Soon As Possible" かどうかを判定
If t.ConstraintType = pjTaskConstraintAsSoonAsPossible Then
' 制約の書き換え
t.ConstraintType = targetConstraint
' 統計用
processedCount = processedCount + 1
End If
End If
Next t
' 3. 正常終了時の後処理
Application.Calculation = pjCalculationAutomatic
Application.ScreenUpdating = True
MsgBox "処理が完了しました。" & vbCrLf & _
"置換されたタスク数: " & processedCount, vbInformation, "完了"
Exit Sub
ErrorHandler:
' 4. エラー発生時のリカバリ
' エラーが起きても、計算モードを「自動」に戻さないと、
' ユーザーは「MS Projectが動かない!」とパニックになる。
Application.Calculation = pjCalculationAutomatic
Application.ScreenUpdating = True
MsgBox "致命的なエラーが発生しました。" & vbCrLf & _
"エラー番号: " & Err.Number & vbCrLf & _
"内容: " & Err.Description, vbCritical, "システムエラー"
End Sub
コードの解説(アーキテクトの視点)
- `Option Explicit`: 変数の宣言を強制する。これは基本中の基本だが、これがないコードは「動いているように見えるだけのゴミ」だ。
- `Application.Calculation = pjCalculationManual`: これがパフォーマンスの鍵だ。計算エンジンを眠らせた状態でデータを一気に書き換え、最後に一括で起こす。
- `If Not t Is Nothing Then`: MS Projectのオブジェクトモデルにおける「守護神」だ。これがないと、ユーザーが途中でタスクを削除したり、空行があったりするだけでツールは死ぬ。
- `ErrorHandler` の設計: プロフェッショナルの仕事は「成功させること」だけでなく、「失敗した時にシステムを安全な状態に戻すこと」にある。計算モードを `Automatic` に戻す処理を `ErrorHandler` に書くのは、ユーザー体験(UX)への配慮だ。
5. さらなる高みへ:データベース連携への注意点
もし君が、この処理の結果を外部のSQL ServerやAccessなどのデータベースに書き戻す設計を考えているなら、さらに注意が必要だ。
1. トランザクションの意識: VBA側で一括置換した後にDBへ書き込む場合、VBA側でエラーが起きたらDB側もロールバック(取り消し)しなければならない。
2. 一括更新(Bulk Update)の検討: タスク一つずつをDBに `UPDATE` 文で飛ばすのは愚策だ。メモリ上に変更リストを保持し、最後に一括でDBに流し込む設計にせよ。
3. IDの不一致: MS Projectの `ID` はユーザーがタスクを並べ替えると変わる。DB連携には必ず不変の `Unique ID` を使用すること。
結論
MS Project VBAにおける自動化とは、単なる「値の書き換え」ではない。「計算エンジンの挙動をいかにコントロールし、計算リソースを最適化するか」という、エンジニアリングの戦いである。
今回学んだ「計算モードの制御」と「堅牢なエラーハンドリング」をマスターすれば、君の作るツールは現場のプロフェッショナルから絶大な信頼を得るだろう。
次に進め。計算エンジンは、君の制御を待っている。
