【Project VBA】「タスク未選択」という深淵――堅牢なオブジェクト取得とメモリ管理の極意
Project VBA(Microsoft ProjectのVBA)を触る際、多くのエンジニアが最初に直面し、そして最後まで悩まされるのが「ユーザーの気まぐれな選択状態」だ。
特に `ActiveSelection` プロパティに依存したコードは、ユーザーがタスクを一つも選択していない瞬間、あるいはビューが非アクティブな瞬間に、容赦なく実行時エラー(Runtime Error 1101等)を吐き出す。これを「エラーハンドリングで逃げる」のは初心者だ。真のアーキテクトは、「状態を確定させ、枯渇したリソースを明示的に解放する」ことで、システムを要塞化する。
今回は、Project VBAにおける「選択範囲の不確実性」を排除し、メモリリークを許さないためのプロフェッショナルな実装手法を伝授する。
—
1. 「ActiveSelection」という脆い依存関係からの脱却
`ActiveSelection.Tasks` を直接参照するコードは、地雷原を歩くようなものだ。Projectのオブジェクトモデルにおいて、`Selection` は常に揮発性である。
真に堅牢なコードを書きたいのであれば、まずは「選択範囲が存在するか」「それはタスクを含むか」という二段構えの検証ルーチンを、カプセル化された関数として定義すべきだ。
堅牢な選択判定コード例
‘ @description 選択範囲が有効なタスクを含んでいるかを検証する
‘ @return {Boolean} 有効なタスクが選択されていればTrue
Public Function IsValidTaskSelection(ByRef pj As Project) As Boolean
‘ Applicationオブジェクトの生存確認
If pj Is Nothing Then Exit Function
‘ ActiveSelection自体がNothingでないか、かつタスクが含まれているかを確認
‘ 選択タイプがpjSelectionTasks(1)であることを厳密にチェックする
If Not pj.ActiveSelection Is Nothing Then
If pj.ActiveSelection.Tasks.Count > 0 Then
IsValidTaskSelection = True
End If
End If
End Function
—
2. オブジェクトのライフサイクルと「隠れたメモリリーク」
VBAはガベージコレクションを搭載しているが、Project VBAにおけるオブジェクト参照は、往々にして「参照カウント」が意図せぬ形で残る。特に、ループ処理内で `Task` オブジェクトを走査し、それを変数に格納し続ける実装は、巨大なプロジェクトファイルではメモリのオーバーヘッドを招く。
メモリ最適化を意識した走査パターン
Public Sub ProcessSelectedTasks()
Dim pj As Project
Dim selTasks As Tasks
Dim tsk As Task
Set pj = ActiveProject
‘ 1. 安全な検証
If Not IsValidTaskSelection(pj) Then
MsgBox “タスクを選択してから実行してください。”, vbCritical
Exit Sub
End If
‘ 2. 参照の局所化と最適化
Set selTasks = pj.ActiveSelection.Tasks
‘ 3. 反復処理
For Each tsk In selTasks
‘ ここで重い処理を行う
‘ 常にオブジェクトの状態を確認してから操作する
If Not tsk Is Nothing Then
Debug.Print “Processing: ” & tsk.Name
End If
Next tsk
‘ 4. 明示的な解放(Project VBAでは必須の作法)
Set tsk = Nothing
Set selTasks = Nothing
Set pj = Nothing
End Sub
—
3. レガシー環境とWindows APIによる「割り込み」回避
大規模なシステム連携においては、VBA単体では解決できない「ウィンドウフォーカスの喪失」が起こる。ユーザーがバックグラウンドで別のExcelを開いた瞬間、`ActiveProject` がNULLを返す現象に遭遇したことはないだろうか。
極限の環境では、`FindWindow` などのWindows APIを呼び出し、対象のProjectがアクティブなプロセスとして確実に存在するかを、VBAのオブジェクトモデルよりも「深い階層」でチェックする必要がある。
If VBA7 Then
Private Declare PtrSafe Function GetForegroundWindow Lib “user32” () As LongPtr
Else
Private Declare Function GetForegroundWindow Lib “user32” () As Long
End If
‘ プロセスがアクティブであるかを確認するアーキテクチャの要点
‘ ActiveProjectに依存せず、APIレベルでWindowHandleを検証することで、
‘ システム間連携時の「予期せぬ実行時エラー」を完全に封殺する。
—
結論:プロフェッショナルとしてのコードを書くために
Project VBAは、他のOffice製品と異なり、計算エンジン(スケジューリング)とUIが密接に結合している。そのため、コードは常に「エンジンが計算中である可能性」や「ユーザーがビューを切り替えた可能性」を考慮しなければならない。
1. 暗黙的な参照を避ける:`ActiveProject` や `ActiveSelection` を連打するな。一度変数に格納し、検証(Is Nothing)を通した上で使用せよ。
2. 型を厳格に定義する:Variant型はメモリ管理の敵である。`Task`, `Tasks`, `Project` 型を明示せよ。
3. 最後に掃除する:`Set obj = Nothing` は、VBAにおける「後始末の美学」だ。これを怠る者は、大規模なタスクリストを扱う資格はない。
現場のコードは、あなたの「論理的な厳密さ」を映し出す鏡だ。エラーを恐れるのではなく、エラーが起こり得ない設計を積み上げる。それが、Project VBAを掌握する唯一の道である。
