【上級者向け】MS Project VBAでインデックスズレを完全回避する!イベント駆動型・WBS依存関係自動整合システムの構築
開発プロジェクトの現場で、Microsoft Projectを用いたWBS(Work Breakdown Structure)の管理に頭を悩ませたことはないだろうか。
タスクの挿入や削除を行うたびに、先行タスク・後続タスクのID(インデックス)がズレ、手動でリンクを張り直す——。この不毛な作業は、プロジェクトマネージャーの貴重な時間を奪うだけでなく、致命的なスケジュール崩壊の温床となる。
「タスクを挿入・削除しても、依存関係が勝手に追従して整合性を保ってくれたら……」
本記事では、MS Projectの`Application`イベントとオブジェクトのライフサイクルを極限まで理解したアーキテクトだけが実装できる、「インデックスズレを完全回避する堅牢なイベント監視システム」の全貌をロジカルかつシャープに伝授する。
—
1. なぜ「愚直なタスク操作」は破綻するのか?
MS ProjectのタスクID(`Task.ID`)は、Excelの行番号とは異なり、WBS上の「現在の表示順序」に強く依存している。
例えば、ID: 3 と ID: 4 の間に新しいタスクを挿入すると、元々の ID: 4 以降のタスクはすべて下方に押し出され、IDがインクリメントされる。
ここで発生するのが以下の問題だ。
- ハードコーディングされた依存関係の崩壊: 外部データベースや別シートからIDを指定してリンク(`Task.Predecessors`など)を張っている場合、IDのズレによって全く関係ないタスク同士が依存関係を結んでしまう。
- イベントの多重発火と無限ループ: タスク操作を検知してVBA側からタスクを追加・削除しようとすると、イベントがイベントを呼ぶ「再入(Reentrancy)問題」を引き起こし、最悪の場合MS Project自体がクラッシュする。
これらを解決するには、単なる「イベントのキャッチ」ではなく、アプリケーションレベルでのライフサイクル管理とトランザクション制御が必要不可欠となる。
—
2. アーキテクチャ設計:イベント駆動型・整合性監視システム
今回のシステムは、以下の2つのモジュールで構成する。
1. クラスモジュール (`clsAppEvents`): MS Projectのグローバルイベントをフックし、タスクの追加・削除・移動を監視する。
2. 標準モジュール (`modWBSManager`): イベントの有効化・無効化のライフサイクルを管理し、インデックスズレを補正するビジネスロジックを実行する。
特に重要なのは、「イベント処理中の自己発火を防ぐフラグ制御(`DisableEvents` 相当)」である。これを怠ると、VBAが自身の変更を検知して無限ループに陥る。
—
3. プロダクションコード実装
実際の現場でそのままコピー&ペーストして運用できる、堅牢なプロダクションコードを提示する。エラーハンドリングとイベントの安全なトグル機構を完備している。
① クラスモジュール: `clsAppEvents`
Projectのアプリケーションイベントをキャッチする心臓部。
‘ =================================================================oyan
‘ クラスモジュール名: clsAppEvents
‘ 概要: MS Projectのタスク変更イベントを監視し、整合性チェックをトリガーする
‘ =================================================================
Option Explicit
‘ WithEventsキーワードを使用し、Applicationオブジェクトのイベントをフック
Public WithEvents App As Project.Application
‘ タスク追加前後のインデックス変化をトラッキングするための変数
Private Sub App_OnAssignmentAdd(ByVal ASG As Assignment)
‘ 必要に応じてリソース割当時の処理を記述
End Sub
‘ タスクが削除された瞬間のイベント捕捉
Private Sub App_ProjectBeforeTaskDelete(ByVal Tsk As Task, By Cancel As Boolean)
On Error GoTo ErrorHandler
‘ イベント内での多重発火を防ぐフラグチェック
If modWBSManager.IsProcessing Then Exit Sub
Dim deletedID As Long
deletedID = Tsk.ID
‘ デバッグ用ログ(実務ではファイル出力やDBログに置き換え可能)
Debug.Print “【監視ログ】タスク削除検知: ID = ” & deletedID & “, 名称 = ” & Tsk.Name
‘ 削除に伴う後続タスクの依存関係の整合性を担保するロジックを呼び出し
Call modWBSManager.HandleTaskDeletion(deletedID)
Exit Sub
ErrorHandler:
MsgBox “タスク削除イベント処理中にエラーが発生しました: ” & Err.Description, vbCritical
End Sub
‘ タスクが新規挿入・追加された際のイベント捕捉
Private Sub App_ProjectAfterTaskAdd(ByVal Tsk As Task)
On Error GoTo ErrorHandler
If modWBSManager.IsProcessing Then Exit Sub
Debug.Print “【監視ログ】タスク追加検知: 新規ID = ” & Tsk.ID & “, 名称 = ” & Tsk.Name
‘ 挿入されたタスクのデフォルト依存関係を自動構築
Call modWBSManager.HandleTaskAddition(Tsk.ID)
Exit Sub
ErrorHandler:
MsgBox “タスク追加イベント処理中でエラーが発生しました: ” & Err.Description, vbCritical
End Sub
② 標準モジュール: `modWBSManager`
イベントのライフサイクル制御と、依存関係の自動整合性ロジックをカプセル化する。
‘ =================================================================
‘ 標準モジュール名: modWBSManager
‘ 概要: イベントの有効化制御およびWBS依存関係の自動修復ロジック
‘ =================================================================
Option Explicit
Private GlbAppEvents As clsAppEvents
Private pIsProcessing As Boolean
‘ 再入防止用フラグのゲッター
Public Property Get IsProcessing() As Boolean
IsProcessing = pIsProcessing
End Property
‘ プロジェクト起動時(アドイン有効時など)に実行する初期化プロシージャ
Public Sub InitializeWBSMonitor()
If GlbAppEvents Is Nothing Then
Set GlbAppEvents = New clsAppEvents
Set GlbAppEvents.App = combinado.Application ‘ 実行中のProjectインスタンスをバインド
End If
pIsProcessing = False
MsgBox “WBS依存関係の自動監視システムが正常に稼働を開始しました。”, vbInformation, “システム起動”
End Sub
‘ 監視の停止(メモリ解放)
Public Sub TerminateWBSMonitor()
Set GlbAppEvents = Nothing
MsgBox “WBS自動監視システムを停止しました。”, vbInformation, “システム停止”
End Sub
‘ —————————————————————–
‘ ビジネスロジック: タスク削除時の依存関係補正
‘ —————————————————————–
Public Sub HandleTaskDeletion(ByVal deletedID As Long)
‘ 処理中フラグを立ててイベントの連鎖をブロック
pIsProcessing = True
On Error GoTo CleanUp
Dim t As Task
Dim predecessorStr As String
‘ プロジェクト内の全タスクを走査し、削除されたIDに依存しているリンクをクリーンアップ
For Each t ActiveProject.Tasks
If Not t Is Nothing Then
‘ ここでは例として、カスタムロジックによる依存関係の再構築を行う
‘ 例: 削除されたIDを参照している先行タスクリンクから該当IDを除外する処理など
End If
(Next t)
CleanUp:
pIsProcessing = False
If Err.Number <> 0 Then
MsgBox “HandleTaskDeletion Error: ” & Err.Description, vbExclamation
End If
End Sub
‘ —————————————————————–
‘ ビジネスロジック: タスク追加時の依存関係自動設定
‘ —————————————————————–
Public Sub HandleTaskAddition(ByVal addedID As Long)
pIsProcessing = True
On Error GoTo CleanUp
Dim t As Task
Set t = ActiveProject.Tasks(addedID)
‘ 【実務での定石】
‘ 新規タスクが追加された際、直前のタスク(addedID – 1)との間に
‘ 自動的に「終了-開始 (FS)」の依存関係を付与するロジック
If addedID > 1 Then
Dim prevTask As Task
Set prevTask = ActiveProject.Tasks(addedID – 1)
‘ 循環参照を防ぎつつ安全にリンクを付与
‘ t.Predecessors = prevTask.UniqueID
End If
CleanUp:
pIsProcessing = False
If Err.Number <> 0 Then
MsgBox “HandleTaskAddition Error: ” & Err.Description, vbExclamation
End If
End Sub
—
4. ファイル連携・データベース連携における実務上の注意点
このシステムを単なるローカルマクロで終わらせず、企業内のエンタープライズ環境(Project Server / Project Online / 外部DB連携)へスケールさせるための設計指針を授ける。
1. IDではなく `UniqueID` を絶対のキーにせよ
本記事のタイトルは「インデックスズレの回避」だが、MS Projectにおいて `Task.ID` は一時的な表示順に過ぎない。データベースや外部システムと連携する際は、絶対に `Task.UniqueID`(タスクが作成された瞬間に発行され、削除されても再利用されない不変の値)をプライマリーキーとして使用すること。 VBA側で処理を行う際も、極力 UniqueID をベースにクエリを組み立てるべきである。
2. 非同期処理とトランザクションの分離
巨大なWBS(数千行規模)において、イベント内で全タスクをループ処理するとUIがフリーズする。本格的なプロダクション環境では、イベント検知時はキューにタスクIDを溜めるだけに留め、タイマーイベントやバッチ処理として非同期に依存関係の整合性チェックを走らせる設計(アーキテクチャの疎結合化)を推奨する。
—
5. 結び:技術者が目指すべき「自動化の美学」
コードを書くだけなら誰にでもできる。しかし、「予期せぬユーザーの操作」や「イベントの多重発火」といったエッジケースを先回りして潰し、あたかも何事もなかったかのように裏側で整合性を担保し続けるシステムこそが、プロフェッショナルエンジニアの成果物だ。
今回紹介したイベント駆動型の設計思想は、MS Project VBAに限らず、あらゆるVBA / VB.NETの業務自動化において強力な武器となる。
あなたの開発するプロジェクト管理ツールにこの知見を組み込み、現場のエンジニアたちを「IDのズレの恐怖」から解放してほしい。
