【入門編】Project VBAにおける「タスクの挿入」時に発生するインデックスズレの完全回避術 – Project VBA解析バイブル

スポンサーリンク

こんにちは!Project VBAの世界へようこそ。

Microsoft Project(以下、MS Project)のマクロを自分で書いて、業務を自動化しようと一歩を踏み出したのですね。素晴らしい挑戦です!

MS Projectは、Excelと似ているようで実は全く異なる「独自の思想」を持ったアプリケーションです。そのため、Excel VBAの感覚でコードを書くと、高確率で「タスクの順序やIDがめちゃくちゃにズレてしまう」という不思議な現象に直面します。

特に、ループ処理の中でタスクを新しく「挿入(追加)」したり「削除」したりする時に、この問題は牙を剥きます。

「あれ? 3行目にタスクを入れたはずなのに、なぜか別のタスクが書き換わってしまった…」
「ループが途中でスキップされて、処理されないタスクがある…」

安心してください。これはあなただけの失敗ではなく、Project VBAに挑戦するすべての人が一度は通る「登竜門」なのです。

この記事では、この「インデックスズレ問題」がなぜ発生するのかという仕組みから、プロのエンジニアが実務で必ず使っている「逆順ループ」「コレクションキャッシュ戦略」という完全回避テクニックまで、優しく、かつ深く解説します。

ここをクリアすれば、Project VBAの基本はバッチリですよ。さあ、一緒にマスターしていきましょう!

1. なぜズレる?「ID」と「UniqueID」の決定的な違い

まずは、MS Projectにおけるタスクの「背番号」の仕組みを理解しましょう。ここが最大のポイントです。
MS Projectのタスクには、大きく分けて2つの識別子(キー)が存在します。

| 識別子 | 特徴 | 例え話 |
| :— | :— | :— |
| ID | 画面に表示されている「行番号」。タスクの挿入・削除、並べ替えによって動的に変動する。 | 学校のクラスでの「出席番号」(転校生が来ると後ろの人の番号がズレる) |
| UniqueID | タスクが作成された瞬間に割り振られる「不変の識別子」。絶対に変動しない。 | 国が発行する「マイナンバー」や学校の「学籍番号」(一生変わらない) |

ズレが発生するメカニズム

例えば、以下のように5つのタスクが並んでいるとします。

[ID: 1] タスクA
[ID: 2] タスクB
[ID: 3] タスクC
[ID: 4] タスクD
[ID: 5] タスクE

ここで、上から順番にループ(`For i = 1 To 5`)を回して、「IDが3(タスクC)の直前に、新しいタスクを挿入する」という処理を行うとどうなるでしょうか?

1周目(i = 1)と 2周目(i = 2)

問題なく「タスクA」「タスクB」が処理されます。

3周目(i = 3)

「タスクC」の手前に、新しい「タスクX」を挿入します。
すると、MS Projectは自動的にID(行番号)を再計算します。

[ID: 1] タスクA
[ID: 2] タスクB
[ID: 3] タスクX <-- 【新しく挿入された!】 [ID: 4] タスクC <-- 【元々のID:3が、4にズレた!】 [ID: 5] タスクD <-- 【元々のID:4が、5にズレた!】 [ID: 6] タスクE <-- 【元々のID:5が、6にズレた!】

4周目(i = 4)

プログラムは次に「ID: 4」のタスクを処理しようとします。
しかし、IDが再計算された結果、ID: 4は「タスクC」になっています。

「あれ? タスクCはさっき3周目で処理するはずだったのに、また処理することになってしまった!」
逆に、元々ID: 4だった「タスクD」は、ID: 5に押し出されたため、このままだと処理がスキップされたり、想定外の挙動を引き起こしたりします。

これが、タスク挿入時に発生する「インデックスズレ(IDズレ)の罠」です。

2. 回避術その1:王道の「逆順(Step -1)ループ」

この問題を解決する最もシンプルでエレガントな方法が、「下から上に向かってループを回す(逆順ループ)」ことです。

なぜ逆順だとズレないのか?

先ほどの例を、今度は下(お尻)から順番に処理してみましょう(`For i = 5 To 1 Step -1`)。

【処理前】
[ID: 1] タスクA
[ID: 2] タスクB
[ID: 3] タスクC
[ID: 4] タスクD
[ID: 5] タスクE

1周目(i = 5)〜 2周目(i = 4)

下から順に「タスクE」「タスクD」を安全に処理します。

3周目(i = 3)

ここで「タスクC(ID: 3)」の直前に「タスクX」を挿入します。

【挿入後】
[ID: 1] タスクA
[ID: 2] タスクB
[ID: 3] タスクX <-- 【新しく挿入された!】 [ID: 4] タスクC <-- 【IDがズレた】 [ID: 5] タスクD <-- 【IDがズレた】 [ID: 6] タスクE <-- 【IDがズレた】

4周目(i = 2)

次に処理するのは「ID: 2(タスクB)」です。
タスクを挿入したことによってIDがズレたのは、「挿入した位置(ID: 3)よりも後ろ(下)のタスクだけ」です。
これから処理する予定の「ID: 2」より上のタスク(タスクA、B)は、一切影響を受けていません!

そのため、何の問題もなく次の処理へと進むことができます。

【実践】逆順ループのVBAコード

それでは、実際のコードを見てみましょう。
このマクロは、「マイルストーン(期間が0日)のタスクを見つけたら、その直前に『前準備』というタスクを自動挿入する」という実用的な例です。

Sub InsertTasksSafety_ReverseLoop()
Dim pj As Project
Set pj = ActiveProject

Dim i As Long
Dim currentTask As Task
Dim newTask As Task

‘ 画面更新を一時停止して処理を高速化します
ScreenUpdating = False

On Error GoTo ErrorHandler

‘ 【ポイント1】後ろ(最大値)から前(1)に向かって Step -1 でループします
For i = pj.Tasks.Count To 1 Step -1

Set currentTask = pj.Tasks(i)

‘ 【ポイント2】MS Projectの空白行(Nothing)をスキップする安全設計
If Not (currentTask Is Nothing) Then

‘ 条件:もしタスクがマイルストーン(期間が0日)だったら
If currentTask.Milestone = True Then

‘ 【ポイント3】Before引数を使って、現在のタスクの直前に挿入
‘ これにより、新タスクが現在のIDを乗っ取り、既存タスクは下に押し出されます
Set newTask = pj.Tasks.Add(Name:=”【自動挿入】前準備タスク”, Before:=i)

‘ 挿入したタスクの初期設定(例として期間を1日に設定)
newTask.Duration = “1d”

‘ 分かりやすいように色を変えておきましょう(任意)
SelectRow Row:=i, RowRelative:=False
Font32Ex Color:=RGB(0, 128, 0) ‘ 緑色に設定

End If
End If
Next i

‘ 画面更新を再開します
ScreenUpdating = True
MsgBox “タスクの挿入が安全に完了しました!”, vbInformation, “完了”
Exit Sub

ErrorHandler:
ScreenUpdating = True
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “エラー”
End Sub

3. 回避術その2:プロ仕様の「UniqueIDキャッシュ戦略」

「逆順ループは便利だけど、WBS(階層構造)のレベルを判定しながら複雑にタスクを追加したり、別のリストと突き合わせたりする場合は、頭が混乱してしまう…」

そんな時のために、より堅牢でプロ仕様のアプローチをご紹介します。それが「UniqueID(ユニークID)キャッシュ戦略」です。

これは、変更されやすい「行番号(ID)」を直接ループで回すのではなく、「絶対に変わらないUniqueID」を一度コレクション(一時的なリスト)に退避(キャッシュ)させ、それを元に処理を行う手法です。

イメージ図

1. 最初にプロジェクト全体のタスクをスキャンし、処理対象の「UniqueID」だけをノート(Collection)に書き留める。
2. ノートに書かれた「UniqueID」を上から順に読み出す。
3. UniqueIDを頼りにタスクを特定し、挿入や削除を実行する(行番号がどれだけズレても、UniqueIDでピンポイントに狙い撃ちできるため、絶対に迷子になりません)。

【実践】UniqueIDキャッシュのVBAコード

Sub InsertTasksSafety_CacheStrategy()
Dim pj As Project
Set pj = ActiveProject

‘ 処理対象のUniqueIDを格納するコレクション
Dim targetUniqueIDs As New Collection
Dim t As Task

‘ 画面更新を停止
ScreenUpdating = False

‘ — ステップ1: 処理対象の「UniqueID」を安全にキャッシュする —
For Each t In pj.Tasks
If Not (t Is Nothing) Then
‘ 例:タスク名に「【重要】」と入っているタスクを対象にする
If InStr(t.Name, “【重要】”) > 0 Then
‘ UniqueIDをコレクションに追加
targetUniqueIDs.Add t.UniqueID
End If
End If
Next t

‘ — ステップ2: キャッシュしたUniqueIDを元に処理を実行する —
Dim uID As Variant
Dim targetTask As Task
Dim newTask As Task

For Each uID In targetUniqueIDs
‘ UniqueIDから現在のタスクオブジェクトを「ピンポイント特定」します
‘ これにより、行番号(ID)がどれだけズレていても正確にヒットします
Set targetTask = pj.Tasks.UniqueID(uID)

If Not (targetTask Is Nothing) Then
‘ 対象タスクの「現在の行番号」を取得して、その直前に挿入
Dim currentID As Long
currentID = targetTask.ID

Set newTask = pj.Tasks.Add(Name:=”[確認用] ” & targetTask.Name & “の事前チェック”, Before:=currentID)
newTask.Duration = “2h” ‘ 2時間
newTask.PercentComplete = 0
End If
Next uID

‘ 画面更新を再開
ScreenUpdating = True
MsgBox “キャッシュ戦略によるタスク挿入が完了しました!”, vbInformation, “完了”
End Sub

4. 陥りやすいエラーと対策チェックリスト

Project VBAを扱う上で、誰もが一度は引っかかる落とし穴とその対策をまとめました。デバッグ時の参考にしてくださいね。

① `Nothing` エラー(エラー番号: 91 / 424)

  • 原因: MS Projectのタスク一覧(`Tasks` コレクション)には、「空白行」が含まれることがあります。空白行をオブジェクトとして操作しようとすると、プログラムが強制終了します。
  • 対策: タスクを取り出したら、必ず `If Not (currentTask Is Nothing) Then` で囲んで、実体が存在するかチェックしてください。

② 無限ループの発生

  • 原因: 通常の「昇順ループ(`For i = 1 To Tasks.Count`)」の中でタスクを挿入し続けると、ループの終了条件である `Tasks.Count` が増え続け、いつまでも処理が終わらない、または意図しないタスクを永久に作り続けることになります。
  • 対策: タスクを「追加・挿入」する場合は、この記事で紹介した「逆順ループ」「UniqueIDキャッシュ」を徹底してください。

③ 画面がチカチカして処理が遅い

  • 原因: タスクを挿入するたびに、MS Projectがガントチャートの描画やスケジュール(日付)の再計算を行うため、動作が非常に重くなります。
  • 対策: 処理の開始時に `ScreenUpdating = False` を行い、終了時に `True` に戻しましょう。これだけで処理速度が数倍〜数十倍に跳ね上がります。

まとめ:Project VBAを掌握する第一歩を踏み出しましたね!

今回は、MS Project VBAの開発で最大の壁となる「タスク挿入時のインデックスズレ問題」について解説しました。

  • 行番号(ID)は動いてしまう「席の番号」。
  • UniqueIDは一生変わらない「名前(マイナンバー)」。
  • タスクを追加・削除するときは、「お尻から処理する(逆順ループ)」か、「UniqueIDをメモしておく(キャッシュ戦略)」

この2つのアプローチをマスターすれば、もうタスク操作でコードが暴走することはありません。Project VBAの基本構造は完全にあなたの手の中にあります。

「マクロの記録」から一歩踏み出し、このようにロジックを自分で組み立てられるようになると、業務自動化の幅は一気に広がりますよ。

あなたのProject VBA開発が、楽しくて実りあるものになりますように。応援しています!

タイトルとURLをコピーしました