オブジェクト指向プログラミングによるガントチャートマクロの作成

5. ガントチャート描画オブジェクト

 これまでに作成したオブジェクトを組み合わせて、工程表を描画するクラスを作成します。名称を「clsGanttChart」として、クラスを作成します。

5-1. 変数宣言

 描画に必要な変数の宣言を指定します。

Option Explicit
 
Private ChartData As New clsCategorys   'データ
Private Schedules As New clsSchedules
 
Private ColNO As Integer        'NO列
Private ColName As Integer      '名称列
Private ColPerson As Integer    '担当者列
Private BeginDate As Date       '工程表開始日
Private BeginRow As Integer     '工程表開始行
Private BeginColumn As Integer  '工程表開始列
Private DrawColumns As Integer  '工程描画列数

5-2. 初期化

 これらの数値を初期化するプロシージャーを作成します。

'変数初期化
Public Sub Initialize()
    
    ColNO = 1
    ColName = 2
    ColPerson = 3
    BeginDate = #1/1/2005#
    DrawColumns = 31
    BeginRow = 5
    BeginColumn = 4
    
End Sub

 本来、外部からこれらの数値を変更できるようにすべきですが、簡易版ということで定数扱いにします。ただし、このプロシージャーを少し変更するだけでとても柔軟にカスタマイズができます。是非、検討してみてください。

5-3. データ読み込み

 次にデータを読み込むプロシージャーを作成します。

'データ読み込み
Private Sub LoadData()
    
    ChartData.Load
    Schedules.Load
    
    Dim i As Long
    Dim j As Long
    
    For i = 1 To Schedules.Count()
        
        j = Schedules.Items(i).Category - 1
        
        If 0 < j And j <= ChartData.Count() Then
            
            Call ChartData.Items(j).Schedules.Add(Schedules.Items(i))
            
        End If
        
    Next
    
End Sub

 ここでは、分類と項目の全データを読み込んでいます。その後、項目の分類NOを調べて、一致する分類の項目集合オブジェクトに項目を登録する操作を行っています。こうすることで、ガントチャートを描画するマクロが非常に簡素になります。

5-4. 描画処理

 次に工程表の描画処理を考えます。まず、工程表の描画中の行を取得するため「ActiveRow」という変数を追加します。

Private ActiveRow As Long

 できているところまでで描画プロシージャーを記述すると、以下のようになります。

'描画
Public Sub Draw()
    
    Call Initialize
    
    Call LoadData
    
    ActiveRow = BeginRow
    
End Sub

5-4-1. 分類表示

 では、分類を表示するプロシージャーを作成します。

'分類の表示
Private Sub PrintCategorys(wk As clsCategorys)
    
    Dim i As Long
    
    For i = 1 To wk.Count()
        
        With ActiveSheet
            .Cells(ActiveRow, ColNO).Value = i
            .Cells(ActiveRow, ColName).IndentLevel = 0
            .Cells(ActiveRow, ColName).Value = wk.Items(i).Name
        End With
        
        ActiveRow = ActiveRow + 1
        
    Next
    
End Sub

 分類集合オブジェクトの持っている分類データの数だけ、分類名を書きます。ここで、セルのプロパティで「IndentLevel = 0」としていますが、これは項目の名称を同じ列に記述するので、その区別をインデントで行うためです。次に作成する項目のインデントは1と設定したいと思います。

5-4-2. 項目表示

 では項目を表示するプロシージャーを作成します。

'項目の表示
Private Sub PrintSchedules(wk As clsSchedules, num As Long)
        
    Dim i As Long
    
    For i = 1 To wk.Count()
        
        With ActiveSheet
            
            .Cells(ActiveRow, ColNO).Value = ""
            .Cells(ActiveRow, ColName).IndentLevel = 1
            .Cells(ActiveRow, ColName).Value = num & "-" & i & ". " & _
                wk.Items(i).Name
            .Cells(ActiveRow, ColPerson).Value = wk.Items(i).Person
            
        End With
        
        ActiveRow = ActiveRow + 1
        
    Next
    
End Sub

 項目集合オブジェクトと番号表示のための数値を引数としています。与えられた項目集合オブジェクトのデータ数だけ、項目名を書き出します。また、番号列の表示を消去し、担当者も合わせて書き出します。

 このプロシージャーを分類表示プロシージャーから呼び出します。変更後は以下のとなります。

'分類の表示
Private Sub PrintCategorys(wk As clsCategorys)
    
    Dim i As Long
    
    For i = 1 To wk.Count()
        
        With ActiveSheet
            .Cells(ActiveRow, ColNO).Value = i
            .Cells(ActiveRow, ColName).IndentLevel = 0
            .Cells(ActiveRow, ColName).Value = wk.Items(i).Name
        End With
        
        ActiveRow = ActiveRow + 1
        
        Call PrintSchedules(wk.Items(i).Schedules, i)
        
    Next
    
End Sub

5-4-3. チャートバーの描画

 ではいよいよチャートバーの描画です。基本的な操作は簡易版ガントチャートで説明した内容と変わりません。ただ、もう少し高度にするため、プロシージャーを少し多くして繰り返す処理を簡素化します。

まず、チャートの描画要・不要を判断するために、工程表の最終日を取得する必要があります。そこで、最終日を取得するプロシージャーを作成します。

'最終日の取得
Private Function EndDate() As Date
    
    EndDate = BeginDate + DrawColumns
    
End Function

最終日を取得できるようになったので、これを用いてチャート表示用プロシージャーを書き始めます。

'チャートの表示
Private Sub PrintChart(wk As clsSchedule)
    
    '予定線
    If (BeginDate <= wk.PlanBegin And wk.PlanEnd <= EndDate) Or _
        (wk.PlanBegin <= BeginDate And EndDate <= wk.PlanEnd) Then
        
        '予定線描画処理
        
    End If
    
    '実績線
    If (BeginDate <= wk.ActionBegin And wk.ActionEnd <= EndDate) Or _
        (wk.ActionBegin <= BeginDate And EndDate <= wk.ActionEnd) Then
    
        '実績線描画処理
    
    End If
    
End Sub

 予定、実績それぞれの開始・終了時間と工程表の期間を判定して、チャートバーの描画が必要か否かを決定します。
 次に予定・実績の線描画処理プロシージャーを作成します。

'チャート用シェイプの追加
Private Function AddChartBar(wkBegin As Date, wkEnd As Date, wkMode As Integer) As Shape
    
    Dim s As Shape
    
    Dim Height As Single
    Dim Top As Single
    Dim Left As Single
    Dim Width As Single
    Dim Fill As Long
    
    With ActiveSheet.Rows(ActiveRow)
    
        Top = .Top
        Height = .Height / 2
        
    End With
    
    Select Case wkMode
        
        Case □□□□□□'予定の場合
            
            Top = Top + 2
            Height = Height - 4
            Fill = RGB(255, 255, 255)
            
        Case □□□□□□'実績の場合
            
            Top = Top + Height + 2
            Height = Height - 4
            Fill = RGB(0, 0, 0)
        
    End Select
    
    Left = □□□□□□'チャートバーの左側取得処理
    Width = □□□□□□'チャートバーの幅取得処理
    
    ActiveSheet.Shapes.AddShape(msoShapeRectangle, Left, Top, Width, Height).Select
    With Selection
        
        .ShapeRange.Fill.ForeColor.RGB = Fill
        Set s = ActiveSheet.Shapes(.Name)
        
    End With
    
    Set AddChartBar = s
    
End Function

 開始日と終了日、そして予定か実績の区別を取得し、それにあったチャートバーを描画します。コード中にある「□□□□□□」は未完成の部分を示します。

 まず、引数にあるwkModeですが、ここで予定は1で実績は2と取り決め

    Select Case wkMode
        
        Case 1	'予定の場合
            
            Top = Top + 2
            Height = Height - 4
            Fill = RGB(255, 255, 255)
            
        Case 2	'実績の場合
            
            Top = Top + Height + 2
            Height = Height - 4
            Fill = RGB(0, 0, 0)
        
    End Select

と書くことができますが、これでは後々わかりにくいかもしれません。そこで、予定と実績をあらわす定数を作成し、それを使用することにします。変数の宣言部に次の2行を追加します。

Private Const MODE_PLAN = 1
Private Const MODE_ACTION = 2

 すると、

    Select Case wkMode
        
        Case MODE_PLAN
            
            Top = Top + 2
            Height = Height - 4
            Fill = RGB(255, 255, 255)
            
        Case MODE_ACTION
            
            Top = Top + Height + 2
            Height = Height - 4
            Fill = RGB(0, 0, 0)
        
    End Select

となり、判読しやすくなります。数ヶ月、数年経過すると自分の書いたコードも意味がわからなくなることがよくあります。そこで、今後のことを考えて、メンテナンス性も重視したプログラミングを行うことはとても重要です。多くの人が「人にわかるコードを書くよう心がけるのは良いこと」と言っていますし、僕自身そう思います。

次に工程表上である日付がどの位置にあるかを取得するプロシージャーを作成します。

'日付から工程表上の位置を取得
Private Function getPosition(wkDate As Date) As Single
    
    Dim Column As Double
    
    Column = wkDate - BeginDate
    
    If Column < 0 Then
        Column = 0
    ElseIf DrawColumns < Column Then
        Column = DrawColumns
    End If
    
    Column = Column + BeginColumn
    
    With ActiveSheet.Columns(Int(Column))
        getPosition = .Left + .Width * (Column - Int(Column))
    End With
    
End Function

 まず、開始日から日付を引きます。0以下なら工程範囲外なので、0に丸めます。また、描画列数以上ならばやはり工程範囲外なので、描画列数に丸めます。取得した列の左端に列幅 x 取得した数値の小数部とすることで、時間単位での位置を取得することが可能となります。これはVB系列の言語において1日が1であることから、このような計算になります。1秒が1のシステムではそれに対応した位置を考えることになります。

 このプロシージャーを用いると、

    Left = getPosition(wkBegin)
    Width = getPosition(wkEnd) - Left

となります。

 簡易版ガントチャートでもチャートバーには名称をつけ、他のシェープと分けましたので、ここでもそうします。そこで勝手ですが、予定なら「CHB_PLAN00000」、実績ならば「CHB_ACTION00000」とします。チャートの名称を指定するのに定数を用いたいと思うので変数宣言部に次の行を追加します。

Private Const CHARTNAME = "CHB_"

 チャートバー追加のプロシージャーはShapeオブジェクトを返すので、このまま名称を変更するような行をチャートバー表示プロシージャーに追加すると、以下のようになります。

'チャートの表示
Private Sub PrintChart(wk As clsSchedule)
    
    '予定線
    If (BeginDate <= wk.PlanBegin And wk.PlanEnd <= EndDate) Or _
        (wk.PlanBegin <= BeginDate And EndDate <= wk.PlanEnd) Then
        
        AddChartBar(wk.PlanBegin, wk.PlanEnd, MODE_PLAN).Name = CHARTNAME & "PLAN" & Format(wk.No, "00000")
        
    End If
    
    '実績線
    If (BeginDate <= wk.ActionBegin And wk.ActionEnd <= EndDate) Or _
        (wk.ActionBegin <= BeginDate And EndDate <= wk.ActionEnd) Then
    
        AddChartBar(wk.ActionBegin, wk.ActionEnd, MODE_ACTION).Name = CHARTNAME & "ACTION" & Format(wk.No, "00000")
    
    End If
    
End Sub

5-4-4. 描画プロシージャーへの追加

 出来上がった部分までを追加します。

'描画
Public Sub Draw()
    
    Call Initialize
    
    Call LoadData
    
    ActiveRow = BeginRow
    
    Call PrintCategorys(ChartData)
    
End Sub

 ですが、このままでは工程表を描画する度にチャートバーが追加され続けますし、同じ名前に設定しようとするとエラーになります。そこで、以下のチャートバー消去プロシージャーを追加して、描画処理のはじめに実行します。

'チャートの消去
Private Sub ClearChartBar()
    
    Dim s As Shape
    
    For Each s In ActiveSheet.Shapes
        
        If s.Name Like CHARTNAME & "*" Then s.Delete
        
    Next
    
End Sub
 
'描画
Public Sub Draw()
    
    Call Initialize
    Call ClearChartBar
    
    Call LoadData
    
    ActiveRow = BeginRow
    
    Call PrintCategorys(ChartData)
    
End Sub

5-5. 描画の実行

 ここまでで、描画に必要な処理は一通り作成終わりました。ですが、実行を選択しても実行するマクロの一覧には何も出ていません。ここまで作成してきたのはクラスであり、実体がありません。そこで、標準モジュールを追加して、そこに描画するためのコードを記述しなくてはなりません。

 名称を「mdlMain」として標準モジュールを追加し、以下のコードを記述します。

Option Explicit
Option Base 1
 
'チャートの描画
Public Sub UpdateChart()
    
    If ActiveSheet.Name <> "チャート" Then Exit Sub
    
    Dim wk As New clsGanttChart
    wk.Draw
    
End Sub

 では、実行してみましょう。でも、その前にデータを入力しなくては何も表示されません。まだ入力の仕組みはできていないので、とりあえず手入力でデータを入力して、工程表を描画してみて下さい。
  うまく描画されましたか? 僕の入力した適当なデータでは以下のようになりました。

 ここまでのファイルをダウンロードできます。