【入門編】【実務中級】Taskの「Predecessors」文字列を解析し、依存関係をグラフ化するためのデータ抽出法 – Project VBA解析バイブル

スポンサーリンク

こんにちは!Project VBAの世界へようこそ。
マクロの記録ボタンを押すだけの時代を卒業し、いよいよ自分の手でMS Projectの巨大なオブジェクトモデルを自在に操るフェーズに踏み込みましたね。素晴らしいステップアップです。

今回は、実務で絶対に避けて通れない「タスク間の依存関係(先行タスク)」をテーマに、VBAを使って美しくデータ化する方法を解説します。

ここをクリアすれば、Project VBAの基本はバッチリですし、何より「プロジェクトの全体像をコードで把握する」というエンジニアとしての快感を味わえますよ。さあ、一緒に深掘りしていきましょう!

なぜ「Predecessors(先行タスク)」の解析が必要なのか?

MS Projectでは、タスク同士をつなぐために「先行タスク(Predecessors)」という概念がありますよね。「タスクBは、タスクAが終わらないと始められない」といったスケジュール制約です。

実務でスケジュール管理をしていると、こう思うことはありませんか?
> 「この複雑なタスクの繋がりを、ビジュアルなグラフ(Graphvizなど)で一気に出力できたら、プロジェクトの全体像がひと目でわかるのに……!」

しかし、ここで一つ大きな壁が立ちふさがります。MS Projectが持つ `Task.Predecessors` というプロパティは、オブジェクトの綺麗なリストではなく、「1FS+2d, 3FF」といった、人間が読むための複雑な“文字列”として返ってくるのです。

この文字列をそのまま外部ツールに放り込んでも、うまく解釈してくれません。だからこそ、VBAの出番です。文字列をスパッと分解し、構造化されたデータに変換する技術をマスターしましょう。

Project VBAの基礎:オブジェクトの階層構造を理解する

MS ProjectのVBAを操る上で、最初に押さえておくべきなのが「オブジェクト階層」です。

Application (アプリ全体)
└── ActiveProject (現在開いているプロジェクト)
└── Tasks (タスクのコレクション)
└── Task (個別のタスク)
├── ID (表示上の番号)
├── UniqueID (一意のID)
├── Name (タスク名)
└── Predecessors (先行タスクの文字列)

Excel VBAと似ていますが、Project特有の癖として、「ID」と「UniqueID」の2種類が存在する点に注意してください。

  • ID: 画面に表示される行番号(タスクを移動すると変わる)
  • UniqueID: MS Projectが内部で管理する不変の番号

依存関係をグラフ化する際は、プロジェクト内で不変である `UniqueID` を軸にデータを構築するのが、プロとしての鉄則です。

実装コード:先行タスク文字列を解析し、グラフデータ(DOT言語)を生成する

今回は、抽出した依存関係を、そのままオープンソースのグラフ描画ツール「Graphviz(DOT言語)」に渡せる形式に整形する実用的なマクロを用意しました。

開発環境(VBE)の標準モジュールに、以下のコードを貼り付けて実行してみてください。

Option Explicit

‘ =================================================================================
‘ 処理名: タスクの依存関係を解析し、Graphviz用DOT言語を出力するプロシージャ
‘ ターゲット: MS Project (実務中級向け)
‘ =================================================================================
Sub ExportTaskDependenciesToGraphviz()

Dim prj As Project
Set prj = ActiveProject

‘ プロジェクトが開かれていない、またはタスクがない場合は終了
If prj.Tasks.Count = 0 Then
MsgBox “解析対象のタスクが存在しません。”, vbExclamation, “警告”
Exit Sub
End If

Dim tsk As Task
Dim dotOutput As String

‘ GraphvizのDOT言語のヘッダーを構築
dotOutput = “digraph ProjectGraph {” & vbCrLf
dotOutput = dotOutput & ” rankdir=LR;” & vbCrLf ‘ 左から右へ流れるグラフ
dotOutput = dotOutput & ” node [shape=box, style=filled, fillcolor=lightblue];” & vbCrLf & vbCrLf

‘ 全タスクをループ処理
For Each tsk In prj.Tasks
‘ サマリータスク(親タスク)や空行は除外する場合のガード(必要に応じて調整)
If Not tsk Is Nothing Then
If tsk.Null = False And tsk.Summary = False Then

‘ Predecessorsプロパティから文字列を取得(例: “2FS+2d, 5″)
Dim predString As String
predString = tsk.Predecessors

If Len(predString) > 0 Then
‘ カンマ区切りで複数の先行タスクを分解する
Dim predArray() As String
predArray = Split(predString, “,”)

Dim i As Long
For i = LBound(predArray) to UBound(predArray)
‘ 前後の空白をトリム
Dim rawPred As String
rawPred = Trim(predArray(i))

‘ 先行タスクの「ID」から「UniqueID」を特定するためのパース処理
‘ ※ ここではシンプル化のため、ID部分の数字だけを抽出する正規表現的なアプローチ
Dim predID As Long
predID = ExtractTaskID(rawPred)

If predID > 0 Then
On Error Resume Next
‘ 先行タスクのIDからオブジェクトを取得し、UniqueIDに変換
Dim predTask As Task
Set predTask = prj.Tasks.UniqueID(prj.Tasks.ID(predID).UniqueID) ‘ 簡易的な安全マージン

‘ エラーが出なければグラフの結線(Edge)を追加
If Err.Number = 0 And Not predTask Is Nothing Then
‘ グラフの定義: “先行タスクUniqueID -> 自タスクUniqueID”
dotOutput = dotOutput & ” T” & predTask.UniqueID & ” -> T” & tsk.UniqueID & “;” & vbCrLf
End If
On Error GoTo 0
End If

Next i
End If

End If
End If
Next tsk

‘ DOT言語のフッターを追加
dotOutput = dotOutput & “}” & vbCrLf

‘ 結果をイミディエイトウィンドウに出力(ファイル出力に拡張も可能)
Debug.Print dotOutput

MsgBox “依存関係の解析が完了しました!イミディエイトウィンドウを確認してください。”, vbInformation, “完了”

End Sub

‘ =================================================================================
‘ 補助関数: 先行タスク文字列から数値(ID)のみを安全に抽出するパーサー
‘ 例: “2FS+2d” -> 2, “12” -> 12
‘ =================================================================================
Private Function ExtractTaskID(ByVal predToken As String) As Long
Dim i As Long
Dim digits As String
digits = “”

‘ 文字列の先頭から数字部分だけを抽出する
For i = 1 To Len(predToken)
Dim c As String
c = Mid(predToken, i, 1)
If c Like “[0-9]” Then
digits = digits & c
Else
‘ 数字以外の文字(FS, SS, +, 日数など)が出てきたらそこでストップ
Exit For
End If
Next i

If Len(digits) > 0 Then
ExtractTaskID = CLng(digits)
Else
ExtractTaskID = 0
End If
End Function

ここでハマる!陥りやすいエラーと対策

初心者がこの領域に踏み込んだとき、必ずと言っていいほどぶpつかる壁が2つあります。

1. 「ID」と「UniqueID」の混同によるオブジェクト迷子

先ほど少し触れましたが、`prj.Tasks(1)` と指定したときの `1` は画面上の行番号(ID)です。ユーザーがタスクの順番をドラッグ&ドロップで入れ替えると、このIDはガラリと変わってしまいます。
ループ処理や外部連携を行うときは、必ず `UniqueID` を経由してオブジェクトを特定する癖をつけましょう。

2. リンク形式(FS, SS, FF, SF)とラグの混入

`Predecessors` が返す文字列には、単純な番号だけでなく `3FS+2d`(終了-開始、2日間のラグ)といった情報が含まれます。
これをそのまま数値として扱おうとすると、VBAが「型が一致しません」と怒り出します(実行時エラー 13)。
今回のサンプルコードにある `ExtractTaskID` 関数のよ​​うに、「数字以外の文字が出てきたらパースをストップする」という防衛的プログラミング(Defensive Programming)が、実務では絶対に不可欠です。

まとめ:あなたのプロジェクトを「見える化」しよう

今回は、Project VBAの少しディープな領域である「Predecessors文字列の解析とデータ抽出」について解説しました。

  • `Predecessors` は便利な反面、文字列としてパースする必要があること
  • タスクの特定には `ID` ではなく `UniqueID` を軸に据えること
  • 予期せぬ文字列(FSやラグ)を弾くパース関数(ロジック)を用意すること

この3つさえ押さえておけば、MS Projectのデータを加工して外部ツールと連携させる基盤は完璧です。イミディエイトウィンドウに出力されたDOT言語をGraphvizのビュワーに貼り付ければ、自分だけの美しいタスクネットワーク図が描き出されますよ。

ぜひ、日々のプロジェクト管理の自動化に役立ててください。
それでは、次のステップでお会いしましょう!

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