こんにちは!Excel VBAやAccess VBAの経験を積んで、次に「Microsoft Project」の自動化に足を踏み入れたあなたへ。
世界最高峰の業務自動化エンジニアとして、今日あなたにお伝えしたいことがあります。
MS ProjectのVBA、特に「選択範囲(ActiveSelection)」の扱いには、知る人ぞ知る恐ろしい罠が潜んでいます。
「マクロの記録」を使うと、どうしても `ActiveSelection` という呪文のようなコードが生成されます。しかし、現場の現場でこれをそのまま実務運用すると、ユーザーが画面でどこを選択しているかによって、マクロが気まぐれにエラーを起こす「爆弾」と化してしまうのです。
今日は、なぜ `ActiveSelection` が危険なのか、そしてプロの現場で使われている「エラーを出さず、狙ったタスクIDを確実に仕留める安全なオブジェクト参照の極意」を、優しく、そして徹底的に伝授します。
ここをクリアすれば、あなたのProject VBAのスキルは一気にプロの領域へ到達しますよ。さあ、始めましょう!
—
1. なぜ「ActiveSelection」は罠なのか?(オブジェクトのライフサイクルを知る)
Excel VBAでも、`Selection` や `ActiveCell` を多用したコードは「不安定になりがち」と言われますよね。
MS Projectの世界では、これがさらにシビアになります。
ユーザーの気まぐれに依存する恐怖
`ActiveSelection` は文字通り、「今、ユーザーが画面上でマウスやキーボードで選択している範囲」を指します。
もし、あなたのマクロが実行された瞬間に……
- ユーザーが何もタスクを選択していなかったら?
- 違うガントチャートのビューを見ていたら?
- タスクではなくリソースを選択していたら?
Projectは冷酷に 「実行時エラー(オブジェクトが見つかりません)」 を投げ、業務中のユーザーの画面をフリーズさせます。これが、マクロの記録をそのまま実務に持ち込んではいけない最大の理由です。
プロが目指すべき境地
私たち自動化エンジニアが目指すべきは、「ユーザーが画面のどこを触っていようが関係なく、コード自身が意図したオブジェクトを確実に掴みに行く(または作る)」という防御的プログラミング(Defensive Programming)です。
—
2. Projectオブジェクトモデルの基本と「ID」の真実
MS Projectのオブジェクト階層は、実は非常にシンプルで美しい構造をしています。
Application
└── Project (ActiveProject)
├── Tasks (コレクション)
│ └── Task (個別のタスク)
└── Resources (コレクション)
└── Resource (個別のリソース)
ここで絶対に覚えておいてほしい、Project VBAの最重要ルールがあります。
> 【極意】タスクを操作するときは、「インデックス番号(行番号)」ではなく「ID」を使え!
インデックス番号 vs ID の違い
- インデックス番号 (`Tasks(1)`, `Tasks(2)`):今、画面上何行目にあるかを示します。行の移動や並び替え、フィルタリングを行うと、この番号はコロコロ変わります。
- ID (`Tasks.UniqueID` や タスクの `ID`):タスクが作成されたときに発行される、いわば「背番号」です。途中で行を入れ替えようがフィルタをかけようが、そのタスク固有のIDは絶対に変わりません。
この「ID」を指名買いできるようになれば、あなたの書くマクロは急にプロの動きを見せ始めます。
—
3. 【実践】安全に特定タスクを操作する防御的コード
それでは、百聞は一見に如かず。
「ユーザーが何も選択していなくても怒られず、指定したタスクID(例: ID=5)の名称を安全に取得してメッセージを出す」という、実務でそのまま使えるコードを見てみましょう。
Sub SafeTaskReferenceSample()
Dim prj As Project
Dim targetTask As Task
Dim targetID As Long
‘ 操作対象のプロジェクトを明確に定義
Set prj = ActiveProject
‘ 万が一、プロジェクトが開いていない場合のガード
If prj Is Nothing Then
MsgBox “現在、有効なプロジェクトが開かれていません。”, vbCritical, “エラー”
Exit Sub
End If
‘ 操作したいタスクのIDを定義(ここでは「ID: 5」を狙い撃ちします)
targetID = 5
‘ 【重要】安全なタスク参照(エラー回避の関数を使用)
Set targetTask = GetTaskByID(prj, targetID)
‘ タスクが見つかったかどうかのチェック(防御的プログラミングの真髄)
If Not targetTask Is Nothing Then
‘ 安全にオブジェクトを操作
MsgBox “ターゲットのタスクを発見しました!” & vbCrLf & _
“タスク名: ” & targetTask.Name & vbCrLf & _
“開始日: ” & targetTask.Start, vbInformation, “成功”
‘ 例:ここでプロパティを変更するなどの処理を書く
‘ targetTask.Text1 = “自動処理完了”
Else
‘ タスクが存在しない場合のハンドリング
MsgBox “指定されたID (” & targetID & “) のタスクは存在しません。”, vbExclamation, “警告”
End If
End Sub
‘ ==========================================================
‘ 【汎用関数】指定したIDのタスクを安全に返す関数
‘ ==========================================================
Function GetTaskByID(pProject As Project, pID As Long) As Task
Dim t As Task
Dim foundTask As Task
Set foundTask = Nothing
‘ For Eachで全タスクを走査し、IDが一致するものを探す
‘ (※ProjectのTaskコレクションはIDで直接引けない場合があるため、ループで安全に探すのが確実です)
For Each t In pProject.Tasks
‘ Nothing判定(空行対策)
If Not t Is Nothing Then
If t.ID = pID Then
Set foundTask = t
Exit For ‘ 見つかったらループを抜ける
End If
End If
Next t
‘ 見つかったタスク(またはNothing)を返す
Set GetTaskByID = foundTask
End Function
—
4. コードの解説:なぜこの書き方が最強なのか?
上記のコードには、現場で生き残るための知恵が詰まっています。ポイントを3つに分けて解説しましょう。
1. `ActiveSelection` を完全に排除した
画面上の選択状態に一切依存せず、背後でしっかりと `ID` をもとにオブジェクトを特定しています。これにより、ユーザーが別のセルをクリックしていてもエラーになりません。
2. `Not t Is Nothing` によるヌルポインタ対策
MS Projectの `Tasks` コレクションには、削除された行の残骸や空行が含まれていることがあり、これが原因で「オブジェクト変数が設定されていません」というエラー(実行時エラー 91)を引き起こします。ループ内で `Not t Is Nothing` を挟むことで、この罠を完全に回避しています。
3. 関数(Helper Function)の分離
「タスクを探す処理」を独立した関数 `GetTaskByID` に切り出しています。これにより、メインのロジックがスッキリし、他のマクロからも流用できるようになります。
—
まとめ:マクロの記録を卒業して、真の自動化エンジニアへ
今回は、Project VBAにおける「選択範囲の罠」と、IDを使った安全なオブジェクト参照の極意について解説しました。
- `ActiveSelection` はユーザー依存の爆弾なので使わない。
- 行番号(インデックス)ではなく、不変の「ID」でターゲットを狙い撃つ。
- `Nothing` 判定を怠らず、防御的なコードを書く。
ここをクリアしたあなたなら、もう「マクロの記録」が吐き出す不安定なコードに怯える必要はありません。どんな巨大なスケジュールファイルを相手にしても、意図通りに正確無比に動く、美しい自動化ツールを作ることができるはずです。
日々の定型業務から解放され、よりクリエイティブなプロジェクト管理に集中するために。ぜひ、あなたの現場のコードにもこの「安全なオブジェクト参照」を取り入れてみてくださいね。応援しています!
