Using Gantt Diagrams in Windows Applications

Environment: Visual Basic, .Net

In this article I will show how to display Gantt Diagrams in Windows applications using a library I wrote to make this activity simple. Gantt diagrams are useful when you have to display things such as 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 a machine). Each of these columns 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 of 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 won't concentrate on the logic where these three activities interact between one another (for example, we assume 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.

Click here for a larger image.

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 that I wrote. 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 the CustomControls library) 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 it how the fixed part is formed, what the entities are, and what the item parts are.

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 to 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 okay 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 that you wrote), 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; call it GanttLib. Right-clicking this tab, 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.

Click here for a larger image.

In the form constructor, after the InitializeComponent call, we can add a call to a function initializing the GanttData object as 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()
  'This call is required by the Windows Form Designer.
  'Added Code
  Init(DateTime.Now, 20)
End Sub

Private Sub Init(ByVal InitialDate As DateTime, ByVal nDays As Integer)

Let us concentrate now on the code we have to write to initialize the GanttData object. We have to define the Items, the fixed columns, and the item columns. The items are the simplest thing 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 an array of the FixedColumnDefinition and ItemColumnDefinition objects. We have to define two arrays of these kinds 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 of three elements (Programmed, Potential, and Maintenance), so an ItemFileds array could be defined with three elements.

In our sample application, we will show the possibility of defining 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 is (it could be 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.

The ItemColumnDefinition and FixedColumnDefinition objects have a parameterless 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 the FixedFieldDefinition objects and ItemFieldDefinition objects is 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 dissertion 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 a 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 a null value is supplied for this parameter, a default function is used.
  • InitObject is a parameter passed to 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 a null value is supplied for this parameter, a default function is used.
  • AllowDbNull says that this column can assume a 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

Seeing that GanttGrid inherits from DataGridEx, we can, for example, add print functionality to our application by calling the 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
    obj2 = obj
    obj = Nothing
.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 used to enable or disable the notification of the 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)
  objGanttGrid.MouseOverNotificationEnabled = True


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 enabled the notification about the cell over which the mouse pointer is. The event handler simply sets the tooltip text with the row and column number.

Another simple functionality to add is the Drag and Drop. The GanttGrid class exposes an event called CellDragDrop, used 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) = _
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 used to manipulate data, we have to examine some low-level details about GanttLib implementation.

GanttGrid is mainly a class derived from DataGridEx and uses DataBinding to display its data. GanttData is a class that inherits 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 perform databinding with DataGridEx. This class has a parameterless 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 by invocating the private method MakeDataSet. If you call the parameterless constructor, none of the initialization values needed to create the DataSet properties is ready and so this constructor doesn't invoke MakeDataSet. In this case, when you set data through FixedFieldDefinitions, ItemFieldDefinitions, and ItemNames, MakeDataSet is invoked. If all the data are 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 databinding 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 databinding through the GetTable method 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 item 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 SIZE="2" COLOR="#0000ff">Property FieldValue(ByVal 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()

Private Sub InitData()
  Dim nr As Integer
r As Integer
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

Final Notes

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

Another note is about the possibility of creating 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 upgrades, questions, bugs found, and so forth. Thanks.


Download Zipped Demo Project Files - 64 Kb.
Download Zipped Library Source Files - 53 Kb.

This article was originally published on September 4th, 2002

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date