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()
InitializeComponent()
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:
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)
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()
objToolTip = New ToolTip()
objToolTip.AutoPopDelay = 5000
objToolTip.InitialDelay = 500
objToolTip.ReshowDelay = 500
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.