【入門編】【初心者向け】ガントチャート上の制約アイコンをVBAで抽出:制約付きタスクのみをリストアップするレポート生成 – Project VBA解析バイブル

スポンサーリンク
スポンサーリンク

【Project VBA入門】ガントチャートの「制約」を可視化せよ:制約付きタスクをExcelへ一括抽出する魔法のコード

こんにちは。プロジェクト管理の世界へようこそ。

MS Projectを使いこなそうとすると、必ずと言っていいほど一つの壁にぶつかります。「スケジュールを動かそうとしているのに、なぜか特定のタスクの日付がビクともしない……」。

その原因の多くは、ガントチャート上に隠れた「制約(Constraints)」にあります。

「マクロの記録」を使って、なんとか操作を自動化しようと奮闘しているあなた。素晴らしい一歩です。でも、今日からはその一歩先へ進みましょう。オブジェクトの性質を理解し、プロジェクトの「意思」を読み解くコードを書く。それが、真の自動化エンジニアへの道です。

今回は、プロジェクトの柔軟性を奪う「制約付きタスク」だけを瞬時にあぶり出し、Excelへリストアップする実用的なVBAテクニックを伝授します。

1. なぜ「制約」を抽出する必要があるのか?

MS Projectのスケジュール計算エンジンは、タスク間の「先行・後続関係(依存関係)」に基づいて、最短・最速のスケジュールを自動計算します。これがプロジェクト管理の醍醐味です。

しかし、そこに「制約」が加わると話は変わります。

  • 「Must Start On(開始日固定)」:どんなに前後の作業が早く終わっても、この日にしか始められない。
  • 「As Late As Possible(できるだけ遅く)」:プロジェクトの終盤まで待機する。

これらは、いわばスケジュールに対する「命令」です。制約が多すぎると、プロジェクトの柔軟性は失われ、クリティカルパスが歪んでしまいます。

「どこに制約がかかっているか」を一覧化することは、スケジュールの健全性を診断するための第一歩なのです。

2. 核心となる概念:`ConstraintType` プロパティ

VBAで制約を判定するために、私たちが使いこなすべきなのが `Task` オブジェクトが持つ `ConstraintType` というプロパティです。

MS Project内部では、制約は数値(定数)として管理されています。例えば、以下のようなイメージです(※実際は列挙型として定義されています)。

定数名 (Enum) 意味
`pjTaskConstraintNone` 制約なし(デフォルト)
`pjTaskConstraintAsSoonAsPossible` できるだけ早く (ASAP)
`pjTaskConstraintMustStartOn` 指定日に開始しなければならない
`pjTaskConstraintStartNoEarlierThan` 指定日より前には開始できない

ここでのポイントは、「制約がない状態(None)」と「デフォルトの ASAP」をどう見分けるかという点にあります。多くのユーザーは、制約を「設定した覚えがあるもの」だけを見つけたいはずです。

3. 実践:制約タスク抽出マクロ

では、実際に動くコードを見ていきましょう。このコードは、ProjectからExcelを立ち上げ、制約がかかっているタスクの名前、制約の種類、開始日を書き出します。

Option Explicit

''' <summary>
''' プロジェクト内の制約付きタスクを抽出し、Excelへ出力する
''' </summary>
Sub ExportConstrainedTasksToExcel()
    Dim t As Task
    Dim xlApp As Object
    Dim xlWB As Object
    Dim xlSheet As Object
    Dim rowCount As Long
    Dim constraintName As String
    
    ' --- 1. Excelの準備 ---
    On Error Resume Next
    Set xlApp = GetObject(, "Excel.Application")
    If xlApp Is Nothing Then
        Set xlApp = CreateObject("Excel.Application")
    End If
    On Error GoTo 0
    
    xlApp.Visible = True
    Set xlWB = xlApp.Workbooks.Add
    Set xlSheet = xlWB.Sheets(1)
    
    ' ヘッダーの作成
    With xlSheet
        .Cells(1, 1).Value = "タスク名"
        .Cells(1, 2).Value = "制約の種類"
        .Cells(1, 3).Value = "開始日"
        .Range("A1:C1").Font.Bold = True
    End With
    
    rowCount = 2
    
    ' --- 2. Projectタスクの走査 ---
    ' ActiveProject内の全タスクをループします
    For Each t In ActiveProject.Tasks
        
        ' 【重要】タスクが存在しない(削除済みなどでNothing)場合はスキップ
        ' これを忘れると、実行時エラーでマクロが止まります
        If Not t Is Nothing Then
            
            ' 制約の種類を判定
            ' pjTaskConstraintAsSoonAsPossible は実質的に「制約なし」に近い挙動のため、
            ' 明示的に設定された制約(None以外かつASAP以外)のみを抽出対象にします
            If t.ConstraintType <> pjTaskConstraintNone And _
               t.ConstraintType <> pjTaskConstraintAsSoonAsPossible Then
                
                ' 制約の種類を分かりやすい文字列に変換
                constraintName = GetConstraintName(t.ConstraintType)
                
                ' Excelへ書き出し
                xlSheet.Cells(rowCount, 1).Value = t.Name
                xlSheet.Cells(rowCount, 2).Value = constraintName
                xlSheet.Cells(rowCount, 3).Value = t.Start
                
                rowCount = rowCount + 1
            End If
        End If
    Next t
    
    ' --- 3. 後処理 ---
    xlSheet.Columns("A:C").AutoFit
    MsgBox "抽出が完了しました!" & vbCrLf & "抽出件数: " & (rowCount - 2) & " 件", vbInformation
    
End Sub

''' <summary>
''' 制約の定数を人間が読める文字列に変換するヘルパー関数
''' </summary>
Private Function GetConstraintName(ByVal cType As Long) As String
    Select Case cType
        Case pjTaskConstraintAsSoonAsPossible: GetConstraintName = "できるだけ早く (ASAP)"
        Case pjTaskConstraintAsLateAsPossible: GetConstraintName = "できるだけ遅く (ALAP)"
        Case pjTaskConstraintMustStartOn:      GetConstraintName = "指定日に開始"
        Case pjTaskConstraintMustFinishOn:     GetConstraintName = "指定日に終了"
        Case pjTaskConstraintStartNoEarlierThan: GetConstraintName = "指定日より前には開始不可"
        Case pjTaskConstraintStartNoLaterThan:  GetConstraintName = "指定日より後には開始不可"
        Case pjTaskConstraintFinishNoEarlierThan: GetConstraintName = "指定日より前には終了不可"
        Case pjTaskConstraintFinishNoLaterThan:   GetConstraintName = "指定日より後には終了不可"
        Case Else:                             GetConstraintName = "その他の制約 (" & cType & ")"
    End Select
End Function

4. プロの視点:ここが「陥りやすい罠」だ!

このコードを読み解く際、単なるコピー&ペーストで終わらせず、以下の3つのポイントを意識してください。これこそが、初級者と中級者を分ける境界線です。

① `If Not t Is Nothing` の重要性

MS Projectの `Tasks` コレクションには、ユーザーが削除したタスクの「抜け殻」のようなオブジェクトが含まれることがあります。もしこのチェックをせずに `t.Name` にアクセスしようとすると、「オブジェクト変数または With ブロック変数が設定されていません」というエラーでマクロが停止します。オブジェクトのライフサイクルを意識した、エンジニアの作法です。

② ASAP(できるだけ早く)の扱い

MS Projectにおいて、制約が設定されていないタスクは、内部的に `pjTaskConstraintAsSoonAsPossible` と見なされます。
もし、単に `t.ConstraintType <> pjTaskConstraintNone` だけを条件にすると、すべてのタスクがリストアップされてしまいます。 私たちが本当に知りたいのは「誰かが意図的に設定した制約」ですよね? だからこそ、ASAPを除外するロジックを入れているのです。

③ Excelとの連携(Late Binding)

上記のコードでは `CreateObject(“Excel.Application”)` を使用しています。これは「レイトバインディング」と呼ばれる手法です。これを使うことで、実行するPCのExcelのバージョンが何であっても、参照設定の手間なく動作させることができます。現場での配布を考えるなら、この書き方がスマートです。

5. おわりに

お疲れ様でした! これであなたは、ガントチャートの「見えない制約」を可視化する強力な武器を手にしました。

プログラミングは、単に命令を機械に伝える作業ではありません。「今、プロジェクトの中で何が起きているのか?」という問いに対して、データで答えを出すための手段です。

今回学んだ「オブジェクトの状態を判定し、条件に合うものだけを抽出する」という流れは、Project VBAに限らず、あらゆる自動化の基本となります。

ここをクリアすれば、Project VBAの基本はバッチリですよ。次は、抽出したデータを元に、制約を自動で解除したり、警告を出したりする「攻め」の自動化に挑戦してみましょう。

応援しています!

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