Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Gantt Diagrams in .NET Windows Applications

0.00/5 (No votes)
21 Aug 2002 1  
In this article, I show a library and its use to display Gantt diagrams in .NET Windows Applications.

Introduction

In this article, I will show how to display Gantt diagrams in .NET Windows applications, using a library written by me to make this activity simple. Gantt diagrams are useful when you have to display things like machine activity in a range of time. Normally there is one or more column indicating the time and one or more column for each entity (such as machine). Each of these column represents some kind of activity. In this article, I will call the first kind of columns the fixed part and the second kind of columns the items part.

For example, let us assume we want to show a diagram for 20 days and for each day we want to show the activity hour by hour. In this case, the fixed part is composed by two columns. We will call them Date and Hour. In the Gantt diagram, we will have 480 rows (20 days * 24 hours/day). In our example, we will have two machines. The first is named First and the second Second. For each machine, we want to represent three kinds of activities, for example Programmed (the machine is programmed for some kind of activity), Potential (the machine is potentially usable) or Maintenance (the machine is not usable because of maintenance). For the sake of simplicity, we don't concentrate in the logic where these three activities interact (for example, we assume that we can have Programmed set to true with Potential set to true or false, when normally this is not a good assumption).

The resulting Gantt diagrams will be similar to that shown below.

The fixed part is formed by columns Date and Hour. Our machines are First and Second and, for each machine, we have an item part formed by columns named Programmed, Potential and Maintenance.

The above image shows the output of the test application we are going to write. This demo application uses three libraries written by me. In this article, we will concentrate on the GanttLib. The second library is named CustomControls and contains an extended DataGrid with print support and other functionality. The third library is named ObjectDumper and contains classes useful to dump objects data in a log file for debugging purposes.

This code is written in Visual Basic mainly because I was writing it for a true application in VB.NET. There is no reason why you cannot use it in a C# or a J# application. The following code is also written in VB.NET, but with some minor changes (mainly because the syntax is different) it will work fine also in other .NET languages.

Gantt Library

The main library used in this article is called GanttLib and hosts a number of classes. The two most important ones are GanttGrid and GanttData. GanttGrid is a class inheriting from DataGridEx (my extended DataGrid defined in library CustomControls) and GanttData is a class containing any definition used to build the Gantt diagram. Typically, in any application using GanttLib, you have to instantiate an object for each of these classes. Then you have to initialize the GanttData object telling him how the fixed part is formed, what are the entities and what are the item parts.

Finally you will set the GanttData property of your GanttGrid object to your GanttData object. At this point, if you show the form hosting the GanttGrid, you will see a grid like that in the image above. Obviously data in the diagram is not initialized. Let us concentrate now on the sample application.

The Sample Application

The first thing we have to do is to create a new Windows Application project with a form. Then we can add, if we like, some menus in this form. Normally the main form can have a Sizable FormBorderStyle.

Then we have to add to our project, a reference to the libraries CustomControls, ObjectDumper and GanttLib (if we already have a compiled version of these files). Another useful way to obtain the same result is to add to our solution, the existing projects CustomControls.vbproj, ObjectDumper.vbproj and GanttLib.vbproj. If we choose this second way, we must add a reference in our main project to projects GanttLib and CustomControls. Pay attention because GanttLib references projects CustomControl and ObjectDumper. (If you create a blank solution containing these three projects, some references may be lost.) It is a good idea to build our solution here to make sure any reference is OK and to build the library hosting the GanttGrid.

Now it's time to add a GanttGrid object and a GanttData object. The simplest way to do this is through the designer in Visual Studio. We can do the same thing by code (if you want to do this, simply look at the code contained in InitializeComponent and copy it in a function written by you), but I think the simplest approach will work fine in every application. First of all, we can add a new tab to the Visual Studio Toolbox: let us call it GanttLib. Right clicking this tab, we will select Customize Toolbox. then we select .Net Framework Components and finally Browse. Now we find the file GanttLib.dll (built with the solution and normally placed in folder bin of the GanttLib directory) and select it. If we close every opened dialog with OK, we will find two controls added to the GanttLib tab. These are GanttData and GanttGrid. If we simply drag and drop these two controls to the form designer, we will obtain two instances of these two classes (we can rename them if we want a name with some sense). Normally the GanttGrid will have the Dock property set to Fill. We can now optionally (we will do it later by code) set the GanttData property of objGanttGrid to objGanttData, using the property window.

The image below shows our project and the two controls in our main form.

In the form constructor, after the InitializeComponent call, we can add a call to a function initializing the GanttData object, like in the code below. This function has two parameters: the first one is the initial date for Gantt diagram creation and the second is the number of days.

Public Sub New()
  MyBase.New()
  'This call is required by the Windows Form Designer.

  InitializeComponent()
  'Added Code 

  Init(DateTime.Now, 20)
End Sub 
Private Sub Init(ByVal InitialDate As DateTime, ByVal nDays As Integer)
  ...
End Sub

Let us concentrate now on the code we have to write in order to initialize the GanttData object. We have to define the items, the fixed columns and the item columns. The items are the simplest things to define. We have to set the ItemNames property to an array of strings containing item names, like in the code below.

objGanttData.ItemNames = New String() {"First", "Second"}

Fixed columns and item columns are defined through properties named FixedFieldDefinitions and ItemFieldDefinitions. These properties are arrays of FixedColumnDefinition and ItemColumnDefinition objects. We have to define two arrays of these kind of objects, initialize them and set the two properties to these arrays.

As we said earlier, we have a fixed part of two items (Date and Hour), so we define a FixedFields array of two elements. The item part is composed by three elements (Programmed, Potential and Maintenance), so a ItemFileds array could be defined with three elements.

In our sample application, we will show the possibility to define hidden columns in Gantt diagrams. This could be useful if we want to show only a boolean field when the field in reality is a code of some kind. For example "Programmed" means the machine is programmed for some activity but we cannot or we don't want to show what activity it is (it could be that the machine is working on some kind of order) and a hidden field could be used to store the activity code. This activity code could be used to show custom tooltips (see below) through a database lookup. For these reasons, we will define the ItemFields array of four elements.

ItemColumnDefinition and FixedColumnDefinition objects have a parameter-less constructor, but, normally, if you need a minimum of functionality, this constructor is useless. Let us see the code.

Dim FixedFields(1) As FixedColumnDefinition
Dim cFDate As New FixedColumnDefinition("Date", _ 
   GetType(System.DateTime), False, _
   Nothing, nDays, True, InitialDate, Nothing, Nothing, 100)
Dim cFHour As New FixedColumnDefinition("Hour", _ 
   GetType(Integer), False, Nothing, _
   24, True, 0, Nothing, Nothing, 50)
FixedFields(0) = (cFDate)
FixedFields(1) = (cFHour)
Dim ItemFields(3) As ItemColumnDefinition
Dim cProgrammed As ItemColumnDefinition
Dim cPotential As ItemColumnDefinition
Dim cMaintenance As ItemColumnDefinition
Dim cCounter As ItemColumnDefinition
cProgrammed = New ItemColumnDefinition("Programmed", _ 
   GetType(Boolean), False, Nothing, False, False, 90, False)
cProgrammed.Color = Color.Red
cPotential = New ItemColumnDefinition("Potential", _ 
   GetType(Boolean), False, Nothing, False, True, 90, False)
cPotential.Color = Color.Green
cMaintenance = New ItemColumnDefinition("Maintenance", _ 
   GetType(Boolean), False, Nothing, False, False, 90, False)
cMaintenance.Color = Color.Blue
cCounter = New ItemColumnDefinition("Counter", _ 
   GetType(Integer), True, Nothing, False, -1, 90, False)
ItemFields(0) = (cProgrammed)
ItemFields(1) = (cPotential)
ItemFields(2) = (cMaintenance)
ItemFields(3) = (cCounter)
objGanttData.ItemFieldDefinitions = ItemFields
objGanttData.FixedFieldDefinitions = FixedFields

As you can see, the constructor used to define FixedFieldDefinition objects and ItemFieldDefinition objects are quite complex.

The code below shows the prototype of these constructors:

'FixedColumnDefinition Constructor: 

Public Sub New(ByVal Name As String, _
  ByVal Type As System.Type, _
  ByVal Hidden As Boolean, _
  ByVal HeaderCreationFunction As CreateHeaderText, _
  ByVal NumberOfElements As Integer, _
  ByVal IsTimeColumn As Boolean, _
  ByVal InitialValue As Object, _
  ByVal EvaluateFunction As EvaluateValue, _
  ByVal InitObject As Object, _
  ByVal PreferredWidth As Integer)

'ItemColumnDefinition Constructor:

Public Sub New(ByVal Name As String, _
  ByVal Type As System.Type, _
  ByVal Hidden As Boolean, _
  ByVal HeaderCreationFunction As CreateHeaderText, _
  ByVal AllowDbNull As Boolean, _
  ByVal DefaultValue As Object, _
  ByVal PreferredWidth As Integer, _
  ByVal IsReadOnly As Boolean)

Let us start our dissection from the FixedColumnDefinition constructor.

  • Name is the name of this column (in our example, Date or Hour).
  • Type is the type of this column (in our example, Date is a column hosting DateTime values and Hour is an Integer column).
  • Hidden says that this column must not be shown.
  • HeaderCreationFunction is the function called by the library to generate the header text for the column. If null value is supplied for this parameter, a default function is used.
  • NumberOfElements is (as the name says) the number of elements generated for this column (in our example, the Hour column hosts 24 elements and the Date hosts a number of columns whose number is a parameter).
  • IsTimeColumn says that following columns must have rows to be repeated for each value of this column.
  • InitialValue is the initialization value in the diagram.
  • EvaluateFunction is the function called to generate subsequent values for each row in the diagram. If null value is supplied for this parameter, a default function is used.
  • InitObject is a parameter passed to the EvaluateFunction each time it is called.
  • PreferredWidth is the default width for this column.

The ItemColumnDefinition constructor has the following parameters.

  • Name is the name of this column (in our example Programmed, Potential, Maintenance or Counter).
  • Type is the type of this column (in our example Counter is an Integer and the other columns are Boolean).
  • Hidden says that this column must not be shown.
  • HeaderCreationFunction is the function called by the library to generate the header text for the column. If null value is supplied for this parameter, a default function is used.
  • AllowDbNull says that this column can assume DbNull value.
  • DefaultValue is the initialization value in the diagram for each row.
  • PreferredWidth is the default width for this column.
  • IsReadonly says that this column cannot be modified.

As the last step, before we can build our application, we have to set the GanttData property of our GanttGrid.

objGanttGrid.GanttData = objGanttData

Adding Functionality to Gantt Diagrams

Seen that GanttGrid inherits from DataGridEx, we can, for example, add print functionality to our application, calling methods PageSetup, PrintPreview and Print as shown below, for the PrintPreview method.

Private Sub mnuPrintPreview_Click(ByVal sender _ 
  As System.Object, ByVal e As System.EventArgs) _ 
  Handles mnuPrintPreview.Click
  
  Dim obj, obj2 As Object
  obj = objGanttGrid.DataSource
  If TypeOf (obj) Is DataView Then
    obj2 = CType(obj, DataView).Table
  Else
    obj2 = obj
    obj = Nothing
  End If
  Me.objGanttGrid.PageSettings = CustomControls.PageSetup.PageSettings
  objGanttGrid.PrintPreview(CType(obj, DataView), CType(obj2, DataTable), _
    CType(Me.BindingContext(objGanttGrid.DataSource), CurrencyManager), 25, _
    "Do you want to view other pages?")
End Sub

A property called MouseOverNotificationEnabled is usable to enable or disable the notification of current cell based on mouse pointer position. If the notification is enabled, every second the mouse position is checked and an event called MouseOverNotification is eventually fired. This can be useful to display custom tooltips based on mouse position, as you can see in the following code:

Private Sub Init(ByVal InitialDate As DateTime, ByVal nDays As Integer)
  CreateMyToolTip()
  objGanttGrid.MouseOverNotificationEnabled = True
  ...
End Sub 
Private objToolTip As ToolTip
Private Sub CreateMyToolTip()
' Create the ToolTip and associate with the Form container.

objToolTip = New ToolTip() 
' Set up the delays for the ToolTip.

objToolTip.AutoPopDelay = 5000
objToolTip.InitialDelay = 500
objToolTip.ReshowDelay = 500
' Force the ToolTip text to be displayed whether or not the form is active.

objToolTip.ShowAlways = True
objGanttGrid.SetToolTip(objToolTip, Nothing)
End Sub
Private Sub objDataGrid_MouseOverNotification(ByVal sender As Object, _
    ByVal e As CustomControls.CellSelectedEventArgs) _
    Handles objGanttGrid.MouseOverNotification
  objGanttGrid.SetToolTip(objToolTip, "(" & e.Row & "," & e.Column & ")")
End Sub

As you can see, in the Init function, we simply called a function named CreateMyToolTip and we enable the notification about the cell over with the mouse pointer is. The event handler simply set tooltip text with the row and column number.

Another simple functionality to add is the Drag and Drop. GanttGrid class exposes an event called CellDragDrop usable to implement copy functionality between item cells of the same type. Drag and Drop functionality must be used with the right mouse button because the left mouse button is already used to modify cell values. The following code shows how to implement this:

Private Sub objGanttGrid_CellDragDrop(ByVal Source As Object,  _
    ByVal Args As GanttLib.CellDragDropEventArgs) _
    Handles objGanttGrid.CellDragDrop
objGanttData.DataTable.Rows(Args.Destination.Row).Item(Args.Destination.Column) _
 = objGanttData.DataTable.Rows(Args.Source.Row).Item(Args.Source.Column)
End Sub

Manipulating Gantt Data

It could be useful to read and write item data to save its values in a database or to initialize the Gantt diagram with meaningful data. To fully understand methods usable to manipulate data, we have to examine some low level details about GanttLib implementation.

GanttGrid is mainly a class derived from DataGridEx and uses data binding to display its data. GanttData is a class inheriting from System.ComponentModel.Component. This is necessary because this class must be visible in the Visual Studio ToolBox. Mainly this class hosts a DataSet used to make data binding with the DataGridEx. This class has a parameter-less constructor used to create an empty class with no data associated. There is another constructor initializing FixedFieldDefinitions, ItemFieldDefinitions and ItemNames. This constructor creates the DataSet and initializes it with its data. This operation is completed invocating the private method MakeDataSet. If you call the parameter-less constructor, none of the initialization values needed to create the DataSet is ready and so this constructor doesn't invoke MakeDataSet. In this case, when you set data through properties FixedFieldDefinitions, ItemFieldDefinitions and ItemNames, MakeDataSet is invoked. If all the data is ready, the DataSet and a DataTable are created, otherwise the method MakeDataSet simply returns. Once the DataSet is ready, the event DataSetReady is fired.

When you set the property GanttData in a GanttGrid object, this causes the creation of a TableStyle and the data binding of the DataTable contained in the DataSet to the GanttGrid. In this way, data hosted in the GanttData object is visible through the grid.

Mainly you can obtain a reference to the DataTable object used for data binding through the method GetTable of your GanttGrid object. You can in this way, manipulate your data in the traditional way. This approach is possible but is quite error prone. The suggested way to manipulate data is through some properties of the GanttData object.

The following table summarizes the most useful methods

Method Description
Public Property ItemNames() As String() Gets or sets item names used to create the DataSet
Public ReadOnly Property ItemName(ByVal ItemNumber As Integer) As String Get the item name, given its index
Public ReadOnly Property NumberOfItems() As Integer Returns the number of items in the items collection
Public Property ModifiedFlag() As Boolean Gets or sets a boolean value indicating the modified state of data in the DataTable
Public Property FixedFieldDefinitions() As FixedColumnDefinition() Gets or sets the fixed fields definition used to create the DataSet
Public Property ItemFieldDefinitions() As ItemColumnDefinition() Gets or sets the item fields definition used to create the DataSet
Public ReadOnly Property NumberOfFixedFields() As Integer Returns the number of fixed fields
Public ReadOnly Property NumberOfFieldsForItem() As Int32 Returns the number of item fields
Public Property FixedFieldValue(ByVal FieldName As String, ByVal Row As Integer) As Object Returns the table value in a certain Row for a given fixed FieldName
Public Property FieldValue(ByVal ItemNumber As Integer, ByVal FieldName As String, ByVal Row As Integer) As Object Returns the table value in a certain Row for a given ItemNumber and a FieldName

The following code shows how to initialize randomly, the Gantt diagram.

Public Sub New()
  ...
  InitData()
End Sub 
Private Sub InitData()
  Dim nr As Integer
  Dim r As Integer
  Dim rnd As New System.Random(DateTime.Now.Millisecond)
  Dim b As Boolean
  nr = objGanttData.DataTable.Rows.Count - 1
  For r = 0 To nr
    b = IIf(rnd.Next(0, 1000) Mod 2 = 0, True, False)
    objGanttData.FieldValue(0, "Programmed", r) = b
    b = IIf(rnd.Next(0, 1000) Mod 2 = 0, True, False)
    objGanttData.FieldValue(0, "Potential", r) = b
    b = IIf(rnd.Next(0, 1000) Mod 2 = 0, True, False)
    objGanttData.FieldValue(0, "Maintenance", r) = b
    objGanttData.FieldValue(0, "Counter", r) = r
    b = IIf(rnd.Next(0, 1000) Mod 2 = 0, True, False)
    objGanttData.FieldValue(1, "Programmed", r) = b
    b = IIf(rnd.Next(0, 1000) Mod 2 = 0, True, False)
    objGanttData.FieldValue(1, "Potential", r) = b
    b = IIf(rnd.Next(0, 1000) Mod 2 = 0, True, False)
    objGanttData.FieldValue(1, "Maintenance", r) = b
    objGanttData.FieldValue(1, "Counter", r) = r
  Next
End Sub

Final Notes

If you want more details about the APIs used in this library, the only thing I can suggest is to look thoroughly in MSDN. Here you can find any details about .NET framework classes.

Another note is about the possibility to create simple Gantt diagrams completely through the Visual Studio designer. You can create simple diagrams entirely through the graphical editor, inserting values for ItemNames, FixedFieldDefinitions and ItemFieldDefinitions.

Please e-mail me for upgrade, questions, bugs found, etc. Thanks.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here