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.

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.

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()
  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 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
  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 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)
  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 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)
= _
   
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 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()
  …
  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 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.

Downloads

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

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read