【Project VBAを掌握する極限の知見】Resourceの「カレンダー」をコードでねじ伏せる:動的例外(Exception)注入の実務解
Microsoft ProjectのVBAにおいて、`Application`、`Project`、`Task`、そして`Resource`のオブジェクトモデルは、一見して直感的に見える。しかし、ひとたび「リソースごとの個別の稼働日カレンダーの上書き(例外日の動的追加)」という実務の壁に直面した途端、多くの開発者はCOMの深い闇に迷い込む。
GUIであれば数クリックで終わる「特定担当者の突発的な休暇や特別稼働日の反映」を、数千件のエンタープライズリソースに対してプログラムから正確に流し込む。この要件を完遂するためには、単なるプロパティの書き換えではなく、CalendarオブジェクトのライフサイクルとException(例外)コレクションのメモリ挙動を完全に制御しなければならない。
本稿では、レガシーなMS ProjectのCOMインターフェースを極限まで最適化し、実務の現場で決して破綻しない堅牢なコードベースを構築するための知見を提示する。
—
1. Projectにおけるカレンダー構造の深層
MS Projectのカレンダー(`Calendar`オブジェクト)は、グローバル、プロジェクト、リソース、そしてタスクという階層構造を持っている。ここでエンジニアが陥る最初の罠が、「Resourceに紐づくBaseCalendarの直接変更」と「Resource独自のCalendar上書き」の混同である。
リソースに個別休暇を反映させる場合、ベースカレンダーを書き換えてはならない。それは全リソースの稼働日に波及してしまう。各リソースが持つ固有の `BaseCalendar` を複製、あるいは直接参照した上で、その `Exceptions` コレクションに対して動的に例外(Exception)を追加するアプローチが必要となる。
ここで重要となるのが、COMオブジェクトの参照管理とメモリリークの防止だ。VBAの暗黙的な参照解放に頼ると、巨大なプロジェクトファイルを開閉するループ処理の中で確実にメモリ肥大化(Bloat)を引き起こす。
—
2. 実装コード:動的例外注入エンジン
以下のコードは、指定されたリソース名に対し、特定の期間を「非稼働日(休暇)」として動的に上書きする実務レベルのプロシージャである。オブジェクトの明示的な解放(`Set obj = Nothing`)を徹底し、長時間のバッチ処理でも安定稼働するよう設計している。
Option Explicit
‘ ==============================================================================
‘ 処理名: 指定リソースへの個別休暇(Exception)動的注入プロシージャ
‘ 概要: 指定されたリソースのカレンダーを取得し、指定期間の例外日を追加する
‘ 著者: Chief Architect (Project VBA Expert)
‘ ==============================================================================
Public Sub InjectResourceException(ByVal targetResourceName As String, _
ByVal exceptionStart As Date, _
ByVal exceptionFinish As Date, _
ByVal exceptionName As String)
Dim proj As MSProject.Project
Dim res As MSProject.Resource
Dim cal As MSProject.Calendar
Dim exts As MSProject.Exceptions
Dim targetEx As MSProject.Exception
Dim isFound As Boolean
isFound = False
‘ 1. アクティブプロジェクトの取得
On Error GoTo ErrorHandler
Set proj = ActiveProject
If proj Is Nothing Then
MsgBox “アクティブなプロジェクトが存在しません。”, vbCritical, “致命的エラー”
Exit Sub
End If
‘ 2. 対象リソースの探索(O(N)の走査におけるオブジェクト参照の最適化)
For Each res in proj.Resources
If Not res Is Nothing Then
If res.Name = targetResourceName Then
isFound = True
Exit For
End If
‘ ループ内でのCOMオブジェクト解放の徹底(メモリ最適化)
Set res = Nothing
End If
Next res
If Not isFound Then
MsgBox “指定されたリソースが見つかりません: ” & targetResourceName, vbExclamation, “警告”
GoTo CleanUp
End If
‘ 3. リソースの個別カレンダー(BaseCalendar)の取得
‘ ※ リソースに独自カレンダーが割り当てられていない場合のフォールバックを考慮
Set cal = res.Calendar
If cal Is Nothing Then
MsgBox “リソースに有効なカレンダーが割り当てられていません。”, vbCritical, “エラー”
GoTo CleanUp
End If
‘ 4. Exceptionsコレクションへのアクセスと例外日の注入
Set exts = cal.Exceptions
‘ 既存の同名・同期間の重複登録を防ぐためのガードロジック
Dim ex As MSProject.Exception
For Each ex In exts
If ex.Name = exceptionName And ex.From = exceptionStart And ex.To = exceptionFinish Then
‘ 既に存在する場合は処理をスキップ
GoTo CleanUp
End If
Set ex = Nothing
Next ex
‘ 新規例外の追加
‘ 引数: Name, [From], [To], [Type], [MinutesFrom], [MinutesTo], [Shift1],[Shift2],[Shift3]
‘ Type 1 = pjExceptionTypeNonWorking (非稼働日)
Set targetEx = exts.Add(Name:=exceptionName, _
From:=exceptionStart, _
To:=exceptionFinish, _
Type:=pjExceptionTypeNonWorking)
Debug.Print “【成功】リソース [” & targetResourceName & “] に例外日を追加しました: ” & _
Format(exceptionStart, “yyyy/mm/dd”) & ” ~ ” & Format(exceptionFinish, “yyyy/mm/dd”)
CleanUp:
‘ 5. オブジェクトの明示的破棄(メモリリークの完全排除)
Set targetEx = Nothing
Set exts = Nothing
Set cal = Nothing
Set res = Nothing
Set proj = Nothing
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました。[Error ” & Err.Number & “]: ” & Err.Description, vbCritical, “System Error”
Resume CleanUp
End Sub
—
3. チーフアーキテクトが指摘する「実務上の罠」と回避策
上記のコードをそのまま社内システムや外部連携バッチに組み込む際、以下の実務的制約に直面する。これらを事前に潰しておくことが、真のプロフェッショナルエンジニアの仕事である。
① 日付の粒度(Time Component)の罠
VBAにおける `Date` 型は時刻を含んでいる(`#2023/10/1 0:00:00#`)。MS Projectのカレンダー例外において、終了日(`To`)の時刻が `0:00:00` の場合、その日の一日が稼働日として扱われてしまうケースがある。
完全な終日休業とするためには、`exceptionFinish` に対して `DateAdd(“s”, -1, DateAdd(“d”, 1, exceptionFinish))` のように時刻を調整するか、API仕様に沿った厳密な日付境界の設定が必要となる。
② 大規模プロジェクトにおけるパフォーマンス低下
`For Each res In proj.Resources` は、プロジェクト内のリソース規模が数千件に達すると、COM境界を跨ぐ通信コストにより著しいパフォーマンス低下を引き起こす。
もし外部システム(人事マスターやERP)から一括で休暇データを流し込む場合は、VBA単体でループを回すのではなく、事前にDictionaryオブジェクトへリソース名とオブジェクト参照をキャッシュする、あるいはADO/API側でフィルタリングをかけた状態で処理を行うアーキテクチャ設計が不可欠である。
③ ガントチャートの再計算(Re-calculation)の遅延
大量のカレンダー変更を一括で行う場合、変更の都度Project側がスケジュール全体の再計算(TaskのAuto-scheduling)を走らせると、実行時間が数倍に跳ね上がる。
バッチ処理の開始前に `Application.Calculation = pjCalculationManual` に設定し、全リソースの処理が完了した後に `pjCalculationAutomatic` に戻して一括再計算を行わせるテクニックは、実務において常識とされるべき最適化手法である。
—
総括
Project VBAにおけるオブジェクト操作は、単に「メソッドを叩くだけ」のレベルでは、実務の巨大なデータ量と複雑な依存関係の前に必ず破綻する。
カレンダーと例外の構造を熟知し、COMのライフサイクルをコントロール下に置き、パフォーマンスのボトルネックを先回りして潰すこと。それらを体現したコードこそが、現場を支える真のエンジニアリングである。
