こんにちは!Project VBAの世界へようこそ。
マクロの記録ボタンを押すだけの日常から一歩踏み出し、「自分の手でプロジェクトの構造を自在に操りたい」と願うあなたへ。
今回は、実務で必ず直面する大きな壁――「WBS(Work Breakdown Structure)の階層構造をプログラムで綺麗に読み解き、外部に出力する」というテーマに挑みます。
ここをクリアすれば、Project VBAの基本はバッチリです。オブジェクトモデルの本質を掴み、ワンランク上のエンジニアへとステップアップしましょう!
—
1. なぜ「再帰処理」と「OutlineChildren」が必要なのか?
MS Projectを開くと、タスクが綺麗にツリー状(階層構造)に並んでいますよね。
「親タスク」の下に「子タスク」があり、その中にはさらに「孫タスク」がいる……。この構造は、人間が見る分には直感的で分かりやすいのですが、ExcelやCSVなどのフラット(平坦)な表に落とし込むときは少し工夫が必要です。
ここで登場するのが、伝説のプロパティ `OutlineChildren` です。
タスクの家族関係を想像してみよう
Project VBAにおいて、すべてのタスクは `Task` オブジェクトという部品でできています。
親タスクの視点に立ってみてください。自分には「子供(直属の子タスク)」が何人かいます。その子供たちにも、さらに子供がいるかもしれません。
これを上から順番に手動で追いかけていくのは、タスク数が100個、1000個と増えたら発狂してしまいますよね。
そこで、「自分に子供がいるなら、子供のところへ行って同じ作業を繰り返す(これを自分自身を呼び出す=再帰呼び出しと言います)」というアルゴリズムを使います。
この「再帰(Recursion)」と `OutlineChildren` を組み合わせることで、どんなに複雑な階層構造のWBSであっても、綺麗に上から順に(あるいは下から順に)舐め尽くすことができるのです。
—
2. 全体像のイメージ:ツリーからリストへの変換
概念を図解してみましょう。
[プロジェクト全体]
┣ 1. 要件定義 (親)
┃ ┣ 1.1 ヒアリング (子)
┃ ┗ 1.2 要件定義書作成 (子)
┗ 2. 設計 (親)
┣ 2.1 外部設計 (子)
┗ 2.2 内部設計 (子)
これを、私たちのプログラムは次のような「フラットなCSVリスト」に変換します。
ID,階層レベル,タスク名,開始日,終了日
1,1,要件定義,2023/10/01,2023/10/10
2,2,ヒアリング,2023/10/01,2023/10/05
3,2,要件定義書作成,2023/10/06,2023/10/10
4,1,設計,2023/10/11,2023/10/20
…
この魔法を実現するコードを、次章で一気に見ていきましょう。
—
3. 実装コード:WBS再帰的CSV出力ツール
MS ProjectのVBE(Visual Basic Editor)を開き、標準モジュールに以下のコードを貼り付けてください。実務ですぐに使えるよう、ファイル出力のエラーハンドリングや文字コードへの配慮も組み込んでいます。
‘ =====================================================================
‘ módulo名: modWBSExporter
‘ 概要: ProjectのWBS階層構造を再帰的に走査し、CSVファイルへ出力する
‘ =====================================================================
Sub ExportWBSToCSV()
Dim filePath As String
Dim fileNum As Integer
Dim tsk As Task
‘ 出力先ファイルのパス(デスクトップの WBS_Export.csv)
filePath = CreateObject(“WScript.Shell”).SpecialFolders(“Desktop”) & “\WBS_Export.csv”
‘ 空きファイル番号を取得
fileNum = FreeFile
‘ CSVファイルを書き込みモードでオープン
Open filePath For Output As #fileNum
‘ ヘッダー行の書き込み
Print #fileNum, “ID,OutlineLevel,Name,Start,Finish,Duration”
‘ アクティブプロジェクトのルートタスク群に対して再帰処理を開始
‘ ※ Project.Tasks はプロジェクト全体のタスクコレクション
For Each tsk In ActiveProject.Tasks
‘ 削除されたタスクやNothing(空スロット)を安全に弾くためのガード
If Not tsk Is Nothing Then
‘ サマリータスク(親)のトップレベル、または独立したタスクからスタート
‘ OutlineLevelが1のものを起点とするのが安全です
If tsk.OutlineLevel = 1 Then
Call TraverseTasks(tsk, fileNum)
End If
End If
Next tsk
‘ ファイルを閉じる
Close #fileNum
MsgBox “WBSのCSV出力が完了しました!” & vbCrLf & “保存先: ” & filePath, vbInformation, “完了”
End Sub
‘ =====================================================================
‘ サブルーチン: 再帰的にタスクを走査しCSVに書き出す心臓部
‘ =====================================================================
Private Sub TraverseTasks(ByVal currentTask As Task, ByVal fileNum As Integer)
Dim childTask As Task
‘ 1. まず「自分自身」の情報をCSVの1行として書き出す
‘ ダブルクォーテーションで囲むことで、タスク名にカンマが含まれてもCSVが壊れないように配慮
Print #fileNum, currentTask.ID & “,” & _
currentTask.OutlineLevel & “,” & _
“””” & currentTask.Name & “””,” & _
currentTask.Start & “,” & _
currentTask.Finish & “,” & _
currentTask.Duration / 480 & “日” ‘ Projectの内部単位(分)日単位(8時間=480分)に変換
‘ 2. 自分に「子タスク」がいるかどうかをチェックする
‘ OutlineChildrenプロパティは、直属の子タスクたちのコレクションを返す
If currentTask.OutlineChildren.Count > 0 Then
‘ 子タスクが複数いるので、1つずつループを回す
For Each childTask In currentTask.OutlineChildren
‘ 【重要】子タスクを引数にして、自分自身(TraverseTasks)をもう一度呼び出す!
Call TraverseTasks(childTask, fileNum)
Next childTask
End If
End Sub
—
4. コードの深掘り解説:ここがプロの勘所
初心者の方が最初に戸惑うポイントと、このコードの美しさをいくつか解説します。
① `If Not tsk Is Nothing Then` というお作法
MS Projectの `Tasks` コレクションは、タスクが途中で削除されたりすると、メモリ上に「空の箱(Nothing)」を残すことがあります。これをそのまま `tsk.Name` などと呼び出すと、無慈悲な 「実行時エラー ’91’: オブジェクト変数が設定されていません」 が発生します。
実務コードでは、`Not tsk Is Nothing` によるガードが必須の作法です。
② `OutlineChildren` と `OutlineLevel` のコンビネーション
- `OutlineLevel`: そのタスクがツリーの何階層目にあるかを教えてくれる数値です(一番上が1)。
- `OutlineChildren`: 直下の子供たちだけを内包するコレクションです。孫以下のタスクは含まれません。
だからこそ、子タスクそれぞれに対して再び `TraverseTasks` を呼び出す(再帰)ことで、孫、ひ孫…と無限の深さまで自動で潜っていくことができるのです。
③ 時間単位の罠 (`Duration` プロパティ)
Project VBAの `Duration` は、人間が目にする「日」や「時間」ではなく、「分(Minutes)」を基準に持っています。そのため、そのまま出力すると「2400」のような大きな数字になってしまいます。
コード内では `currentTask.Duration / 480` として、1日(8時間 = 480分)で割って「日単位」に直しているのが、実務で役立つ細かな配慮です。
—
5. よくあるエラーと回避のコツ
- Q. 実行してもファイルに何も書き込まれない、あるいは一部のタスクが抜ける
- A. ルートの絞り込み条件(`If tsk.OutlineLevel = 1 Then`)を確認してください。MS Projectの構成によっては、トップレベルにプロジェクト名サマリーが存在し、その下がすべてレベル1になっている場合があります。もし「プロジェクト自体」がタスクとして含まれるのが嫌な場合は、`ActiveProject.SummaryTask.OutlineChildren` を起点にするのも一つの手です。
- Q. CSVをExcelで開いたときに文字化けする
- A. VBAの `Open … For Output` はデフォルトでShift-JIS(ANSI)で出力します。もし最新のExcelや他システム連携でUTF-8が必要な場合は、ADODB.Streamオブジェクトを使ってストリーム書き込みを行う高度な実装に拡張してみてください。まずは今回の基本形を完璧にマスターするのが近道です。
—
まとめ
いかがでしたでしょうか?
「再帰処理」と聞くと難解な数学のパズルのように聞こえますが、実態は「親子関係のバトンタッチをプログラムに自動でやらせているだけ」です。
ここをクリアできれば、Projectのデータを自由自在に加工・集計し、社内のBIツールやExcelレポートへシームレスに連携できるようになります。あなたの業務自動化スキルは、間違いなく「中級者」の領域に到達しています。
ぜひ、日々のプロジェクト管理の現場でこのコードを役立ててみてください。
それでは、次回の極限知見でお会いしましょう!
