聖域の崩壊:なぜ「ActiveProject」に命を預けてはならないのか
MS ProjectにおけるVBA開発において、初心者が最初に手に取り、そして熟練者が最も忌避するプロパティがある。それが `Application.ActiveProject` だ。
複数のプロジェクトファイル(.mpp)を並行して開くマルチプロジェクト運用は、大規模開発の現場では日常茶飯事である。しかし、VBAコード内で `ActiveProject` を安易に使用することは、システムの制御を「ユーザーのマウス操作」という極めて不安定な外部要因に委ねることを意味する。
コードが実行されている最中にユーザーが別のウィンドウをクリックした瞬間、あなたの丹精込めたロジックは、意図しない別のプロジェクトのタスクやリソースを破壊し始める。これは「バグ」ではない。設計思想の「敗北」である。
本稿では、Projectオブジェクトを確実に捕捉し、メモリの深淵まで制御下に置くための、プロフェッショナルのみが知る流儀を解説する。
—
1. 静寂なる参照:インデックスと名前による特定
`ActiveProject` を捨て、我々が採用すべきは「明示的参照」である。プロジェクトを特定する方法は主に3つあるが、その使い分けには設計上の意図が必要だ。
プロジェクト名による直接捕捉
プロジェクトが開かれていることが保証されている場合、`Projects` コレクションから名前で取得するのが最も確実である。
‘ プロジェクト名(ファイル名)をキーにオブジェクトを固定する
Dim targetProj As MSProject.Project
On Error Resume Next
Set targetProj = Application.Projects(“核心システム開発_rev2.mpp”)
On Error GoTo 0
If targetProj Is Nothing Then
‘ 異常系処理:プロジェクトが開かれていない場合のハンドリング
Exit Sub
End If
インデックスによる反復処理
特定の条件(プロパティやカスタムフィールドの値)に基づいてプロジェクトを特定する場合、コレクションを走査する。この際、`ActiveProject` は一切無視する。
—
2. Win32 APIを用いたインスタンスの厳格な識別
複数のMS Projectインスタンスが立ち上がっている特殊な環境や、OSレベルでのウィンドウ制御が必要な場合、VBA標準機能だけでは限界がある。シニアエンジニアであれば、`HWND`(ウィンドウハンドル)を介した制御を検討すべきだ。
MS Projectの `Application.WindowHandle` や `Project.Index` を組み合わせることで、プロセス空間における自己の立ち位置を確定させる。
If VBA7 Then
‘ 64bit環境対応のAPI宣言
Declare PtrSafe Function GetWindowText Lib “user32” Alias “GetWindowTextA” (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long
Else
‘ 32bit環境用
Declare Function GetWindowText Lib “user32” Alias “GetWindowTextA” (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
End If
”’
”’
Public Sub ValidateProjectContext()
Dim currentHwnd As LongPtr
currentHwnd = Application.WindowHandle
Dim buffer As String 255
Dim length As Long
length = GetWindowText(currentHwnd, buffer, 255)
Dim windowTitle As String
windowTitle = Left(buffer, length)
‘ デバッグ出力:現在OSが認識しているウィンドウタイトル
Debug.Print “Current Project Window: ” & windowTitle
End Sub
—
3. オブジェクトのライフサイクル管理とメモリ最適化
Project VBAにおいて、COMオブジェクトの解放漏れは致命的だ。特に `Project` オブジェクトや `Tasks` コレクションを操作した後、それらがメモリ上に残り続けると、`WINPROJ.EXE` がゴーストプロセスとして居座り、次回起動時の挙動を不安定にさせる。
黄金律:Set Nothing の徹底
以下のコードは、複数のプロジェクトを安全に巡回し、リソースを確実に解放するためのテンプレートである。
Public Sub ProcessAllProjectsSafely()
Dim proj As MSProject.Project
Dim tsk As MSProject.Task
‘ Applicationレベルの再描画停止(パフォーマンス向上とユーザー操作の介入防止)
Application.ScreenUpdating = False
Try
For Each proj In Application.Projects
‘ ActiveProjectではなく、ループ変数projを起点に全ての操作を行う
Debug.Print “Processing: ” & proj.Name
For Each tsk In proj.Tasks
If Not tsk Is Nothing Then
‘ タスクごとの処理
‘ …
End If
Next tsk
Next proj
Catch:
‘ エラーハンドリング
Debug.Print “Error: ” & Err.Description
Finally:
‘ オブジェクトの明示的解放(逆順序が基本)
Set tsk = Nothing
Set proj = Nothing
‘ 描画再開
Application.ScreenUpdating = True
End Try
End Sub
—
4. レガシー環境と現代的設計の邂逅
長年運用されている .mpp ファイルには、過去の遺物とも言えるマクロが混在していることが多い。これらを保守する際、最も危険なのは「グローバル変数に保持されたProjectオブジェクト」である。
システム間連携(ExcelからのMS Project操作など)を行う場合、以下の設計指針を死守せよ。
1. CreateObject / GetObject の峻別: 既存のインスタンスを再利用するのか、クリーンな環境を生成するのかを明確にする。
2. Visibleプロパティの制御: 自動処理中は `Application.Visible = False` を検討し、ユーザーの物理的な介入(クリックによるActiveProjectの変更)を物理的に遮断する。
3. イベント駆動の抑制: `Application.EnableEvents = False` により、意図しない `Project_Activate` 等のイベント連鎖を断ち切る。
—
結論:アーキテクトとしての矜持
`Application.ActiveProject` を使うことは、暗闇の中で手探りでスイッチを探すようなものだ。対して、オブジェクトを変数に固定し、そのスコープとライフサイクルを完全に掌握することは、設計図に基づいた確実な建築に等しい。
「動けばいい」というコードは、現場に混沌をもたらす。我々シニアエンジニアが書くべきは、10年後の保守担当者がその論理的整合性に感嘆するような、静謐で強固なコードである。
Project VBAの真髄は、オブジェクトモデルの「深さ」にある。その深淵を覗き込み、Activeという名の不確実性を排除したとき、あなたのシステムは真の安定を手にするだろう。
