こんにちは!プロジェクト管理の現場で、日々VBAと格闘お疲れ様です。
「マクロの記録」から一歩踏み出し、自力でコードを書けるようになってくると、必ずぶ-つかる壁がありますよね。
そう、「大規模なプロジェクトファイルを扱った途端、マクロの処理がカメのように遅くなる問題」です。
数百、数千というタスク(Task)が入り組んだWBS(Work Breakdown Structure)を操作するとき、うっかりやってしまいがちなのが、「ループのたびに `ActiveProject.Tasks(i)` を呼び出す」というコード。実はこれ、Project VBAにおける最大のタブーの一つなんです。
今回は、数千タスクの巨龍をまるでトカゲのように軽快に操るための極意、「Taskオブジェクトのキャッシュ戦略」をあなたに伝授します。ここをクリアすれば、あなたのVBAスキルは間違いなくプロの領域に到達しますよ。
—
1. なぜ、巨龍(大規模WBS)の処理は遅くなるのか?
まずは敵を知ることから始めましょう。
MS ProjectのVBAにおいて、`ActiveProject.Tasks` というコレクションは、VBAの世界と、背後でうごめくC++ベースのProjectのコアエンジンを繋ぐ「連絡橋」のようなものです。
ループを回して以下のようなコードを書いたとします。
‘ ❌ やってはいけない典型例
Dim i As Long
For i = 1 to ActiveProject.Tasks.Count
‘ ループのたびにVBAとProjectのエンジン間で「通訳」が発生する!
Debug.Print ActiveProject.Tasks(i).Name
Next i
このコード、タスクが5,000件あったら何が起きると思いますか?
VBAはタスクの名前を取得するために、「おい、5,000番目のタスクのデータをこっちによこせ!」と、わざわざ毎回エンジンに連絡橋を渡って行き来しているのです。この「COMオブジェクトへのアクセスコスト」が、処理を劇的に重くする元凶です。
2. 解決の切り札:オブジェクトの「キャッシュ戦略」とは?
ここで登場するのがキャッシュ(Cache:一時保存)戦略です。
考え方はとてもシンプル。
「最初にエンジンから必要なデータを全部引き出し、VBAのメモリ空間(配列やコレクション)に一気に放り込んでおく。あとは、手元にあるそのメモリ上で高速に料理する」
これだけで、エンジンとの無駄な往復(オーバーヘッド)を根絶やしにできます。具体的には、Variant型の配列(Array)にタスクのプロパティを流し込むか、強力なオブジェクト参照の配列を作って保持します。
—
3. 実践!数千タスクを秒速で料理するキャッシュ実装コード
それでは、現場でそのまま使える実用コードを見ていきましょう。
今回は、数千件あるタスクの「ID」「名前」「前提条件(先行タスク)」をメモリ上に一気にキャッシュし、依存関係の整合性を爆速でチェックするプロシージャです。
Option Explicit
Sub FastTaskCacheSample()
Dim tStart As Double
tStart = Timer ‘ 処理時間計測用
Dim tCount As Long
tCount = ActiveProject.Tasks.Count
If tCount = 0 Then
MsgBox “タスクが存在しません。”, vbExclamation
Exit Sub
End If
‘ 【極意その1】画面描画を停止し、エンジンを黙らせる(必須の高速化テクニック)
Application.ScreenUpdating = False
On Error GoTo ErrorHandler
‘ 【極意その2】メモリ上で扱うための「二次元配列」を用意する
‘ 1列目: ID, 2列目: Name, 3列目: タスクオブジェクト自体の参照
Dim taskData() As Variant
ReDim taskData(1 To tCount, 1 To 3) As Variant
Dim t As Task
Dim i As Long
i = 1
‘ 【極意その3】For Each で一気にスキャンし、VBAの配列に閉じ込める
For Each t In ActiveProject.Tasks
If Not t Is Nothing Then
taskData(i, 1) = t.ID
taskData(i, 2) = t.Name
Set taskData(i, 3) = t ‘ ★オブジェクト参照そのものをキャッシュする!
End If
i = i + 1
Next t
‘ — ここから先は、Projectのエンジンではなく「メモリ(配列)」と会話する —
Dim processedCount As Long
processedCount = 0
For i = 1 To tCount
‘ 配列の3番目(キャッシュしたTaskオブジェクト)を呼び出す
Dim cachedTask As Task
Set cachedTask = taskData(i, 3)
‘ 例:タスク名に特定のキーワードが含まれていたら処理を行う
If Not cachedTask Is Nothing Then
If InStr(cachedTask.Name, “【重要】”) > 0 Then
‘ ここでプロパティの書き換えや依存関係の操作を行う
‘ cachedTask.Predecessors = … など
processedCount = processedCount + 1
End If
End If
Next i
‘ 処理終了後の後始末
Application.ScreenUpdating = True
MsgBox “処理が完了しました!” & vbCrLf & _
“総タスク数: ” & tCount & vbCrLf & _
“該当タスク: ” & processedCount & vbCrLf & _
“処理時間: ” & Format(Timer – tStart, “0.00”) & ” 秒”, vbInformation
Exit Sub
ErrorHandler:
‘ エラー時も確実に画面描画を戻す(これがないとフリーズしたように見えるので注意)
Application.ScreenUpdating = True
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical
End Sub
コードの重要ポイント解説
1. `Application.ScreenUpdating = False`
VBAがセルやタスクを1行変更するたびに、画面の表示を更新しようとする無駄な動きをシャットアウトします。これだけでも数倍速くなります。
2. `Set taskData(i, 3) = t`
ここがミソです。値だけでなく、オブジェクトへの参照(ポインタ)そのものを配列に格納しています。これにより、後続の処理でプロジェクトのタスクツリーに何度もアクセスせず、メモリ上から一瞬で該当タスクを引っ張ってこられます。
3. エラーハンドリングと `ScreenUpdating` のセット
もし途中でエラーが起きても、必ず `ScreenUpdating = True` を通るように `On Error GoTo` を組むのが、プロのエンジニアの作法です。
—
4. 陥りやすい罠と注意点
キャッシュ戦略は強力ですが、一つだけ注意すべきルールがあります。
> ⚠️ 注意:キャッシュ内で「タスクの削除や追加」を行ってはならない
タスクを途中で削除したり新規追加したりすると、配列内に保存していたオブジェクトの参照(メモリ上の住所のようなもの)と、実際のProject上のタスクの整合性が狂い、最悪の場合VBAが強制終了(クラッシュ)します。
- 読み取り・プロパティの変更・依存関係のチェック:キャッシュを使って爆速で処理する
- タスクの追加・削除・構造の大幅な組み替え:ループの構造自体を工夫し、最小限の操作に留める
この使い分けを頭に入れておいてください。
—
まとめ:さあ、あなたのWBSを軽やかに舞わせよう
今回は、Project VBAにおけるコレクション操作の高速化、そしてTaskオブジェクトのキャッシュ戦略について解説しました。
- ループ内で毎回 `ActiveProject.Tasks(i)` を叩かない。
- 一度配列やメモリにオブジェクト参照をキャッシュする。
- `ScreenUpdating` を制御して無駄な描画コストを削る。
この手法を取り入れるだけで、今までイライラさせられていた巨大なプロジェクトファイルの処理が、嘘のように一瞬で終わるようになります。
「ここをクリアすれば、Project VBAの基本はバッチリですよ!」
ぜひ、あなたの現場のWBS自動化ツールにこの知見を取り入れ、まわりのエンジニアをあっと言わせてください。応援しています!
