Features
- ASP.NET Gantt Chart control
- Drag and drop (task moving, resizing, link creating, row moving)
- Dependencies (task links)
- Task groups
- Milestones
- Task duration in a special column
- Context menu for task creating and deleting
- Visual Studio sample solution
- C# source code
- VB.NET source code
- Includes a trial version of DayPilot Pro for ASP.NET WebForms
License
Licensed for testing and evaluation purposes. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for ASP.NET WebForms. Buy a license.
Create a Gantt Chart
We will use the ASP.NET Gantt Chart control from DayPilot Pro for ASP.NET WebForms package.
<DayPilot:DayPilotGantt runat="server" ID="Gantt"> </DayPilot:DayPilotGantt>
New Task Row
Now we will enable row creating using RowCreateHandling property:
RowCreateHandling="CallBack"
CallBack handling type means the Gantt control will call the server-side RowCreate event using an AJAX CallBack.
<DayPilot:DayPilotGantt runat="server" ID="Gantt" RowCreateHandling="CallBack" > </DayPilot:DayPilotGantt>
As soon as the users finishes entering the new row name the Gantt control will fire RowCreate event. We will come back to RowCreate event handler later, as soon as we connect the Gantt chart to a database.
Read more about task creating [doc.daypilot.org].
Connect the Gantt Chart to a Database
We will use the following [task] table to store the tasks in the database:
CREATE TABLE [dbo].[task] ( [id] INT IDENTITY (1, 1) NOT NULL, [name] VARCHAR (50) NULL, [start] DATETIME NOT NULL, [end] DATETIME NOT NULL, [parent_id] INT NULL, [ordinal] INT NULL, [ordinal_priority] DATETIME NULL, [milestone] BIT DEFAULT ((0)) NOT NULL, CONSTRAINT [PK_task] PRIMARY KEY CLUSTERED ([id] ASC) );
In order to display the task hierarchy we need to following fields:
- id - task id
- name - task name
- start - task start
- end - task end
- parent_id - id of the task parent, null for the root tasks
The Gantt chart control will let you specify the field names and load the tree hierarchy from a flat data source.
C#
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { LoadTasksAndLinks(); } } private void LoadTasks() { Gantt.DataStartField = "start"; Gantt.DataEndField = "end"; Gantt.DataIdField = "id"; Gantt.DataTextField = "name"; Gantt.DataParentField = "parent_id"; Gantt.DataMilestoneField = "milestone"; Gantt.DataSource = Db.GetTasksFlat(); Gantt.DataBind(); }
VB
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) If Not IsPostBack Then LoadTasksAndLinks() End If End Sub Private Sub LoadTasks() Gantt.DataStartField = "start" Gantt.DataEndField = "end" Gantt.DataIdField = "id" Gantt.DataTextField = "name" Gantt.DataParentField = "parent_id" Gantt.DataMilestoneField = "milestone" Gantt.DataSource = Db.GetTasksFlat() Gantt.DataBind() End Sub
The Db.GetTasksFlat() method loads the flat task data.
C#
public static DataTable GetTasksFlat() { SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM [task] order by [ordinal], [ordinal_priority] desc", ConfigurationManager.ConnectionStrings["daypilot"].ConnectionString); DataTable dt = new DataTable(); da.Fill(dt); return dt; }
VB
Public Shared Function GetTasksFlat() As DataTable Dim da As New SqlDataAdapter("SELECT * FROM [task] order by [ordinal], [ordinal_priority] desc", ConfigurationManager.ConnectionStrings("daypilot").ConnectionString) Dim dt As New DataTable() da.Fill(dt) Return dt End Function
Read more about task loading [doc.daypilot.org].
Create a New Gantt Task
In order to create a new task in the database, we need to add a handler for the RowCreate event.
<DayPilot:DayPilotGantt runat="server" ID="Gantt" RowCreateHandling="CallBack" OnRowCreate="Gantt_OnRowCreate" > </DayPilot:DayPilotGantt>
This Gantt_OnRowCreate event handler will create a new database record and refresh the view.
C#
protected void Gantt_OnRowCreate(object sender, RowCreateEventArgs e) { DateTime start = Gantt.StartDate; DateTime end = start.AddDays(1); string text = e.Text; int maxOrdinal = Db.GetMaxOrdinal(null); Db.InsertTask(start, end, text, null, maxOrdinal + 1); LoadTasksAndLinks(); Gantt.UpdateWithMessage("Task created.", CallBackUpdateType.Full); }
VB
Protected Sub Gantt_OnRowCreate(ByVal sender As Object, ByVal e As RowCreateEventArgs) Dim start As Date = Gantt.StartDate Dim [end] As Date = start.AddDays(1) Dim text As String = e.Text Dim maxOrdinal As Integer = Db.GetMaxOrdinal(Nothing) Db.InsertTask(start, [end], text, Nothing, maxOrdinal + 1) LoadTasksAndLinks() Gantt.UpdateWithMessage("Task created.", CallBackUpdateType.Full) End Sub
The Db.InsertTask() method creates a new task in the database. We will use "null" as the parent id because the new task will be stored at the top level.
C#
public static int InsertTask(DateTime start, DateTime end, string name, string parent, int ordinal) { using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["daypilot"].ConnectionString)) { con.Open(); SqlCommand cmd = new SqlCommand("INSERT INTO [task] ([start], [end], [name], [parent_id], [ordinal], [ordinal_priority]) VALUES(@start, @end, @name, @parent, @ordinal, @priority)", con); cmd.Parameters.AddWithValue("start", start); cmd.Parameters.AddWithValue("end", end); cmd.Parameters.AddWithValue("name", name); cmd.Parameters.AddWithValue("parent", (object)parent ?? DBNull.Value); cmd.Parameters.AddWithValue("ordinal", ordinal); cmd.Parameters.AddWithValue("priority", DateTime.Now); cmd.ExecuteNonQuery(); cmd = new SqlCommand("select @@identity;", con); int id = Convert.ToInt32(cmd.ExecuteScalar()); return id; } }
VB
Public Shared Function InsertTask(ByVal start As Date, ByVal [end] As Date, ByVal name As String, ByVal parent As String, ByVal ordinal As Integer) As Integer Using con As New SqlConnection(ConfigurationManager.ConnectionStrings("daypilot").ConnectionString) con.Open() Dim cmd As New SqlCommand("INSERT INTO [task] ([start], [end], [name], [parent_id], [ordinal], [ordinal_priority]) VALUES(@start, @end, @name, @parent, @ordinal, @priority)", con) cmd.Parameters.AddWithValue("start", start) cmd.Parameters.AddWithValue("end", [end]) cmd.Parameters.AddWithValue("name", name) cmd.Parameters.AddWithValue("parent", If(DirectCast(parent, Object), DBNull.Value)) cmd.Parameters.AddWithValue("ordinal", ordinal) cmd.Parameters.AddWithValue("priority", Date.Now) cmd.ExecuteNonQuery() cmd = New SqlCommand("select @@identity;", con) Dim id As Integer = Convert.ToInt32(cmd.ExecuteScalar()) Return id End Using End Function
Gantt Columns
The Gantt control can display additional task data in custom columns.
<DayPilot:DayPilotGantt runat="server" ID="Gantt" ... OnBeforeTaskRender="Gantt_OnBeforeTaskRender" > <Columns> <DayPilot:TaskColumn Title="Task" Property="text" Width="100" /> <DayPilot:TaskColumn Title="Duration" Width="100" /> </Columns> </DayPilot:DayPilotGantt>
The columns will display data from a specified database field (Property). The column data can be further customized using BeforeTaskRender event handler. We will use it to display the task duration in the second column.
C#
protected void Gantt_OnBeforeTaskRender(object sender, BeforeTaskRenderEventArgs e) { TimeSpan duration = e.End - e.Start; e.Row.Columns[1].Html = duration.ToString(); }
VB
Protected Sub Gantt_OnBeforeTaskRender(ByVal sender As Object, ByVal e As BeforeTaskRenderEventArgs) Dim duration As TimeSpan = e.End - e.Start e.Row.Columns(1).Html = duration.ToString() End Sub
Read more about Gantt columns [doc.daypilot.org].
Drag and Drop Gantt Task Moving
Drag and drop task moving is enabled by default (TaskMoveHandling="Notify"). This feature allows the users to move the Gantt tasks in time. We need to add a TaskMove event handler to save the changes in the database:
<DayPilot:DayPilotGantt runat="server" ID="Gantt" OnTaskMove="Gantt_OnTaskMove" ... />
When TaskMoveHandling property is set to "Notify" (default value) the Gantt chart updates the task position on the client side before firing the server-side event handler. This means it isn't necessary to reload all tasks in the TaskMove event handler.
However, moving the task to a new position can affect the row header content generated using BeforeTaskRender. That's why we reload all tasks and request a full refresh of the Gantt Chart in Gantt_OnTaskMove method.
C#
protected void Gantt_OnTaskMove(object sender, TaskMoveEventArgs e) { Db.UpdateTask(e.Id, e.NewStart, e.NewEnd, e.Text); LoadTasksAndLinks(); Gantt.Update(CallBackUpdateType.Full); }
VB
Protected Sub Gantt_OnTaskMove(ByVal sender As Object, ByVal e As TaskMoveEventArgs) Db.UpdateTask(e.Id, e.NewStart, e.NewEnd, e.Text) LoadTasksAndLinks() Gantt.Update(CallBackUpdateType.Full) End Sub
Read more about drag and drop task moving [doc.daypilot.org].
Drag and Drop Gantt Task Resizing
Drag and drop task resizing is enabled by default (TaskResizeHandling="Notify"). This feature allows the users to change the start or duration of the tasks. We need to add a TaskResize event handler to save the changes in the database:
<DayPilot:DayPilotGantt runat="server" ID="Gantt" OnTaskResize="Gantt_OnTaskResize" ... />
When TaskResizeHandling property is set to "Notify" (default value) the Gantt chart updates the task start and/or duration on the client side before firing the server-side event handler. This means it isn't necessary to reload the tasks in the TaskResize event handler.
Just as in case of EventMove, resizing the task can affect the row header content generated using BeforeTaskRender. That's why we reload all tasks and request a full refresh of the Gantt Chart in Gantt_OnTaskResize method.
C#
protected void Gantt_OnTaskResize(object sender, TaskResizeEventArgs e) { Db.UpdateTask(e.Id, e.NewStart, e.NewEnd, e.Text); LoadTasksAndLinks(); Gantt.UpdateWithMessage("Resized", CallBackUpdateType.Full); }
VB
Protected Sub Gantt_OnTaskResize(ByVal sender As Object, ByVal e As TaskResizeEventArgs) Db.UpdateTask(e.Id, e.NewStart, e.NewEnd, e.Text) LoadTasksAndLinks() Gantt.UpdateWithMessage("Resized", CallBackUpdateType.Full) End Sub
Read more about drag and drop task resizing [doc.daypilot.org].
Drag and Drop Row Moving (Moving Tasks in the Tree Hierarchy)
The ASP.NET Gantt chart control supports drag and drop changes of the task hierarchy. A drag handle appears on the left side of the task header if the user hovers over its name.
The user can select the target position using dragging. The Gantt chart will let them place the item before or after another item, or as a child.
When the task is dropped the task will be moved to the new location.
You can row moving using RowMoveHandling property:
<DayPilot:DayPilotGantt runat="server" ID="Gantt" ... RowMoveHandling="Notify" />
We will use two special database fields to store the task order in the database: [ordinal] and [ordinal_priority]. We will update the order after every row move and compact the sequence of [ordinal] values for every parent task.
C#
protected void Gantt_OnRowMove(object sender, RowMoveEventArgs e) { const int max = Int32.MaxValue; // make sure it's greater than any other ordinal value Task sourceParent = Gantt.Tasks.FindParent(e.Source); Task targetParent = Gantt.Tasks.FindParent(e.Target); //DataRow sourceRow = DbGetTask(e.Source.Id); DataRow targetRow = Db.GetTask(e.Target.Id); string targetParentId = targetParent != null ? targetParent.Id : null; string sourceParentId = sourceParent != null ? sourceParent.Id : null; int targetOrdinal = (int) targetRow["ordinal"]; switch (e.Position) { case RowMovePosition.Before: Db.UpdateTaskParent(e.Source.Id, targetParentId, targetOrdinal); break; case RowMovePosition.After: Db.UpdateTaskParent(e.Source.Id, targetParentId, targetOrdinal + 1); break; case RowMovePosition.Child: Db.UpdateTaskParent(e.Source.Id, e.Target.Id, max); targetParentId = e.Target.Id; break; case RowMovePosition.Forbidden: break; default: throw new ArgumentOutOfRangeException(); } Db.CompactOrdinals(sourceParentId); if (sourceParentId != targetParentId) { Db.CompactOrdinals(targetParentId); } }
VB
Protected Sub Gantt_OnRowMove(ByVal sender As Object, ByVal e As RowMoveEventArgs) Const max As Integer = Int32.MaxValue ' make sure it's greater than any other ordinal value Dim sourceParent As Task = Gantt.Tasks.FindParent(e.Source) Dim targetParent As Task = Gantt.Tasks.FindParent(e.Target) 'DataRow sourceRow = DbGetTask(e.Source.Id); Dim targetRow As DataRow = Db.GetTask(e.Target.Id) Dim targetParentId As String = If(targetParent IsNot Nothing, targetParent.Id, Nothing) Dim sourceParentId As String = If(sourceParent IsNot Nothing, sourceParent.Id, Nothing) Dim targetOrdinal As Integer = DirectCast(targetRow("ordinal"), Integer) Select Case e.Position Case RowMovePosition.Before Db.UpdateTaskParent(e.Source.Id, targetParentId, targetOrdinal) Case RowMovePosition.After Db.UpdateTaskParent(e.Source.Id, targetParentId, targetOrdinal + 1) Case RowMovePosition.Child Db.UpdateTaskParent(e.Source.Id, e.Target.Id, max) targetParentId = e.Target.Id Case RowMovePosition.Forbidden Case Else Throw New ArgumentOutOfRangeException() End Select Db.CompactOrdinals(sourceParentId) If sourceParentId <> targetParentId Then Db.CompactOrdinals(targetParentId) End If End Sub
The [ordinal] field stores the task order for the same parent task (stored in [parent_id]). The [ordinal_priority] stores the last update date and time and is used temporarily during the reordering. It keeps the algorithm simpler.
We also have to update the task loading method to use the [ordinal] field for sorting the tasks.
C#
public static DataTable GetTasksFlat() { SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM [task] order by [ordinal], [ordinal_priority] desc", ConfigurationManager.ConnectionStrings["daypilot"].ConnectionString); DataTable dt = new DataTable(); da.Fill(dt); return dt; }
VB
Public Shared Function GetTasksFlat() As DataTable Dim da As New SqlDataAdapter("SELECT * FROM [task] order by [ordinal], [ordinal_priority] desc", ConfigurationManager.ConnectionStrings("daypilot").ConnectionString) Dim dt As New DataTable() da.Fill(dt) Return dt End Function
Read more about drag and drop row moving [doc.daypilot.org].
Task Context Menu
We will add a task context menu that will let the users perform the following actions:
- Create a child task
- Edit task details in a modal dialog
- Convert the task to a milestone
- Delete the task
The context menu (and its items) is defined using a DayPilotMenu control:
<DayPilot:DayPilotMenu runat="server" id="ContextMenuTask" ClientIDMode="Static"> <MenuItems> <DayPilot:MenuItem Text="Add Child Task..." Action="JavaScript" JavaScript="createChild(this.source);"></DayPilot:MenuItem> <DayPilot:MenuItem Text="-"></DayPilot:MenuItem> <DayPilot:MenuItem Text="Edit..." Action="JavaScript" JavaScript="edit(this.source)"></DayPilot:MenuItem> <DayPilot:MenuItem Text="Convert to Milestone" Action="CallBack" Command="ToMilestone"></DayPilot:MenuItem> <DayPilot:MenuItem Text="Delete" Action="CallBack" Command="Delete"></DayPilot:MenuItem> </MenuItems> </DayPilot:DayPilotMenu>
We will assign the context menu to the Gantt control using ContextMenuRowID property:
<DayPilot:DayPilotGantt runat="server" ID="Gantt" ... ContextMenuRowID="ContextMenuTask" OnRowMenuClick="Gantt_OnRowMenuClick" />
The context menu items with Action="CallBack" will be handled on the server side using RowMenuClick event.
C#
protected void Gantt_OnRowMenuClick(object sender, RowMenuClickEventArgs e) { switch (e.Command) { case "Delete": Db.DeleteTaskWithChildren(e.Task.Id); LoadTasksAndLinks(); Gantt.Update(); break; case "ToMilestone": Db.ToMilestone(e.Task.Id); LoadTasksAndLinks(); Gantt.Update(); break; case "ToTask": Db.ToTask(e.Task.Id); LoadTasksAndLinks(); Gantt.Update(); break; } }
VB
Protected Sub Gantt_OnRowMenuClick(ByVal sender As Object, ByVal e As RowMenuClickEventArgs) Select Case e.Command Case "Delete" Db.DeleteTaskWithChildren(e.Task.Id) LoadTasksAndLinks() Gantt.Update() Case "ToMilestone" Db.ToMilestone(e.Task.Id) LoadTasksAndLinks() Gantt.Update() Case "ToTask" Db.ToTask(e.Task.Id) LoadTasksAndLinks() Gantt.Update() End Select End Sub
By default the row context menu can be invoked using a right click. In order to provide a visual hint to the users we will add an active area to the row using BeforeTaskRender event handler.
C#
protected void Gantt_OnBeforeTaskRender(object sender, BeforeTaskRenderEventArgs e) { TimeSpan duration = e.End - e.Start; e.Row.Columns[1].Html = duration.ToString(); e.Row.Areas.Add(new Area().Width(14).Height(14).Right(2).Top(2).ContextMenu("ContextMenuTask").Style("cursor: pointer; box-sizing: border-box; background: white; border: 1px solid #ccc; background-repeat: no-repeat; background-position: center center; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABASURBVChTYxg4wAjE0kC8AoiFQAJYwFcgjocwGRiMgPgdEP9HwyBFDkCMAtAVY1UEAzDFeBXBAEgxQUWUAgYGAEurD5Y3/iOAAAAAAElFTkSuQmCC);")); }
VB
Protected Sub Gantt_OnBeforeTaskRender(ByVal sender As Object, ByVal e As BeforeTaskRenderEventArgs) Dim duration As TimeSpan = e.End - e.Start e.Row.Columns(1).Html = duration.ToString() e.Row.Areas.Add((New Area()).Width(14).Height(14).Right(2).Top(2).ContextMenu("ContextMenuTask").Style("cursor: pointer; box-sizing: border-box; background: white; border: 1px solid #ccc; background-repeat: no-repeat; background-position: center center; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABASURBVChTYxg4wAjE0kC8AoiFQAJYwFcgjocwGRiMgPgdEP9HwyBFDkCMAtAVY1UEAzDFeBXBAEgxQUWUAgYGAEurD5Y3/iOAAAAAAElFTkSuQmCC);")) End Sub
The context menu actions should be different for task groups and milestones. We will create a special context menu control for each task type.
<DayPilot:DayPilotMenu runat="server" id="ContextMenuGroup" ClientIDMode="Static"> <MenuItems> <DayPilot:MenuItem Text="Add Child Task..." Action="JavaScript" JavaScript="createChild(this.source);"></DayPilot:MenuItem> <DayPilot:MenuItem Text="-"></DayPilot:MenuItem> <DayPilot:MenuItem Text="Edit..." Action="JavaScript" JavaScript="edit(this.source)"></DayPilot:MenuItem> <DayPilot:MenuItem Text="Delete" Action="CallBack" Command="Delete"></DayPilot:MenuItem> </MenuItems> </DayPilot:DayPilotMenu> <DayPilot:DayPilotMenu runat="server" id="ContextMenuMilestone" ClientIDMode="Static"> <MenuItems> <DayPilot:MenuItem Text="Edit..." Action="JavaScript" JavaScript="edit(this.source)"></DayPilot:MenuItem> <DayPilot:MenuItem Text="Convert to Task" Action="CallBack" Command="ToTask"></DayPilot:MenuItem> <DayPilot:MenuItem Text="Delete" Action="CallBack" Command="Delete"></DayPilot:MenuItem> </MenuItems> </DayPilot:DayPilotMenu>
We will modify BeforeTaskRender again and assign the context menu depending on the task type.
C#
protected void Gantt_OnBeforeTaskRender(object sender, BeforeTaskRenderEventArgs e) { TimeSpan duration = e.End - e.Start; e.Row.Columns[1].Html = duration.ToString(); switch (e.Type) { case TaskType.Milestone: e.Row.ContextMenuClientName = "ContextMenuMilestone"; e.Row.Areas.Add(new Area().Width(14).Height(14).Right(2).Top(2).ContextMenu("ContextMenuMilestone").Style("cursor: pointer; box-sizing: border-box; background: white; border: 1px solid #ccc; background-repeat: no-repeat; background-position: center center; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABASURBVChTYxg4wAjE0kC8AoiFQAJYwFcgjocwGRiMgPgdEP9HwyBFDkCMAtAVY1UEAzDFeBXBAEgxQUWUAgYGAEurD5Y3/iOAAAAAAElFTkSuQmCC);")); break; case TaskType.Group: e.Row.ContextMenuClientName = "ContextMenuGroup"; e.Row.Areas.Add(new Area().Width(14).Height(14).Right(2).Top(2).ContextMenu("ContextMenuGroup").Style("cursor: pointer; box-sizing: border-box; background: white; border: 1px solid #ccc; background-repeat: no-repeat; background-position: center center; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABASURBVChTYxg4wAjE0kC8AoiFQAJYwFcgjocwGRiMgPgdEP9HwyBFDkCMAtAVY1UEAzDFeBXBAEgxQUWUAgYGAEurD5Y3/iOAAAAAAElFTkSuQmCC);")); break; case TaskType.Task: e.Row.Areas.Add(new Area().Width(14).Height(14).Right(2).Top(2).ContextMenu("ContextMenuTask").Style("cursor: pointer; box-sizing: border-box; background: white; border: 1px solid #ccc; background-repeat: no-repeat; background-position: center center; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABASURBVChTYxg4wAjE0kC8AoiFQAJYwFcgjocwGRiMgPgdEP9HwyBFDkCMAtAVY1UEAzDFeBXBAEgxQUWUAgYGAEurD5Y3/iOAAAAAAElFTkSuQmCC);")); break; } }
VB
Protected Sub Gantt_OnBeforeTaskRender(ByVal sender As Object, ByVal e As BeforeTaskRenderEventArgs) Dim duration As TimeSpan = e.End - e.Start e.Row.Columns(1).Html = duration.ToString() Select Case e.Type Case TaskType.Milestone e.Row.ContextMenuClientName = "ContextMenuMilestone" e.Row.Areas.Add((New Area()).Width(14).Height(14).Right(2).Top(2).ContextMenu("ContextMenuMilestone").Style("cursor: pointer; box-sizing: border-box; background: white; border: 1px solid #ccc; background-repeat: no-repeat; background-position: center center; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABASURBVChTYxg4wAjE0kC8AoiFQAJYwFcgjocwGRiMgPgdEP9HwyBFDkCMAtAVY1UEAzDFeBXBAEgxQUWUAgYGAEurD5Y3/iOAAAAAAElFTkSuQmCC);")) Case TaskType.Group e.Row.ContextMenuClientName = "ContextMenuGroup" e.Row.Areas.Add((New Area()).Width(14).Height(14).Right(2).Top(2).ContextMenu("ContextMenuGroup").Style("cursor: pointer; box-sizing: border-box; background: white; border: 1px solid #ccc; background-repeat: no-repeat; background-position: center center; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABASURBVChTYxg4wAjE0kC8AoiFQAJYwFcgjocwGRiMgPgdEP9HwyBFDkCMAtAVY1UEAzDFeBXBAEgxQUWUAgYGAEurD5Y3/iOAAAAAAElFTkSuQmCC);")) Case TaskType.Task e.Row.Areas.Add((New Area()).Width(14).Height(14).Right(2).Top(2).ContextMenu("ContextMenuTask").Style("cursor: pointer; box-sizing: border-box; background: white; border: 1px solid #ccc; background-repeat: no-repeat; background-position: center center; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABASURBVChTYxg4wAjE0kC8AoiFQAJYwFcgjocwGRiMgPgdEP9HwyBFDkCMAtAVY1UEAzDFeBXBAEgxQUWUAgYGAEurD5Y3/iOAAAAAAElFTkSuQmCC);")) End Select End Sub
Creating Milestones
Milestone is a special type of task that can be used to mark a specified point in time.
We won't let the users create the milestones directly - they are able to convert a new task to a milestone using a context menu.
We store the milestone flag in the database in [milestone] boolean field:
C#
private void LoadTasks() { Gantt.DataStartField = "start"; Gantt.DataEndField = "end"; Gantt.DataIdField = "id"; Gantt.DataTextField = "name"; Gantt.DataParentField = "parent_id"; Gantt.DataMilestoneField = "milestone"; Gantt.DataSource = Db.GetTasksFlat(); Gantt.DataBind(); }
VB
Private Sub LoadTasks() Gantt.DataStartField = "start" Gantt.DataEndField = "end" Gantt.DataIdField = "id" Gantt.DataTextField = "name" Gantt.DataParentField = "parent_id" Gantt.DataMilestoneField = "milestone" Gantt.DataSource = Db.GetTasksFlat() Gantt.DataBind() End Sub
Task Dependencies (Links)
The Gantt chart supports links that show task dependencies. Common link types (Finish-to-Start, Finish-to-Finish, Start-to-Start, Start-to-Finish) are supported.
Links can be loaded using Links property.
C#
private void LoadLinks() { Gantt.Links.Clear(); DataTable data = Db.GetLinks(); foreach (DataRow row in data.Rows) { Link link = new Link(Convert.ToString(row["from"]), Convert.ToString(row["to"])); link.Id = Convert.ToString(row["id"]); link.Type = LinkTypeParser.Parse((string) row["type"]); Gantt.Links.Add(link); } }
VB
Private Sub LoadLinks() Gantt.Links.Clear() Dim data As DataTable = Db.GetLinks() For Each row As DataRow In data.Rows Dim link As New Link(Convert.ToString(row("from")), Convert.ToString(row("to"))) link.Id = Convert.ToString(row("id")) link.Type = LinkTypeParser.Parse(DirectCast(row("type"), String)) Gantt.Links.Add(link) Next row End Sub
Drag and Drop Link Creating
The Gantt control supports creating task links using drag and drop. This feature is enabled by default (LinkCreateHandling="Notify"). We need to create a LinkCreate event handler in order to create a DB record for the new link.
<DayPilot:DayPilotGantt runat="server" ID="Gantt" ... OnLinkCreate="Gantt_OnLinkCreate" />
The Gantt_OnLinkCreate updates the database. We need to reload the events on the client side. Although the link would be created on the client side in the default "Notify" handling mode it would have no ID associated to it.
C#
protected void Gantt_OnLinkCreate(object sender, LinkCreateEventArgs e) { Db.InsertLink(e.FromId, e.ToId, e.Type.ToString()); LoadTasksAndLinks(); Gantt.Update(CallBackUpdateType.Full); }
VB
Protected Sub Gantt_OnLinkCreate(ByVal sender As Object, ByVal e As LinkCreateEventArgs) Db.InsertLink(e.FromId, e.ToId, e.Type.ToString()) Dim fullRefresh As Boolean = True If fullRefresh Then LoadTasksAndLinks() Gantt.Update(CallBackUpdateType.Full) End If End Sub
Link Deleting
We will let the users delete links using a context menu. First, we will create a context menu control and link it to the Gantt control using ContextMenuLinkID property.
<DayPilot:DayPilotGantt runat="server" ID="Gantt" ... ContextMenuLinkID="ContextMenuLink" OnLinkMenuClick="Gantt_OnLinkMenuClick" /> <DayPilot:DayPilotMenu ID="ContextMenuLink" runat="server"> <DayPilot:MenuItem Text="Delete" Action="Callback" Command="Delete"></DayPilot:MenuItem> </DayPilot:DayPilotMenu>
The "Delete" menu item will fire LinkMenuClick event on the server side.
C#
protected void Gantt_OnLinkMenuClick(object sender, LinkMenuClickEventArgs e) { switch (e.Command) { case "Delete": Db.DeleteLink(e.Id); LoadTasksAndLinks(); Gantt.Update(); break; } }
VB
Protected Sub Gantt_OnLinkMenuClick(ByVal sender As Object, ByVal e As LinkMenuClickEventArgs) Select Case e.Command Case "Delete" Db.DeleteLink(e.Id) LoadTasksAndLinks() Gantt.Update() End Select End Sub
Read more about link context menu [doc.daypilot.org].
Displaying Task Progress
The Gantt chart includes built-in support for displaying task progress. It is displayed graphically in the complete bar at the top of the task box. The task also displays the complete percentage as text in the task box.
The complete percentage can be loaded directly from the database using DataCompleteField property:
C#
private void LoadTasks() { Gantt.DataStartField = "start"; Gantt.DataEndField = "end"; Gantt.DataIdField = "id"; // ... Gantt.DataCompleteField = "complete"; Gantt.DataSource = Db.GetTasksFlat(); Gantt.DataBind(); }
VB
Private Sub LoadTasks() Gantt.DataStartField = "start" Gantt.DataEndField = "end" Gantt.DataIdField = "id" ' ... Gantt.DataCompleteField = "complete" Gantt.DataSource = Db.GetTasksFlat() Gantt.DataBind() End Sub
Read more about task percent complete [doc.daypilot.org].