Working Hours in GanttView
Environment
| Product Version | Product | Author |
|---|---|---|
| 2020.3.1020 | RadGanttView for WinForms | Desislava Yordanova |
Description
This article demonstrates a sample approach how to simulate working hours in RadGanttView. The time slots outside the range are not visible.

Solution
This can be achieved with the help of a Custom Timeline that shows the hours from 9:00 to 18:00. In order to align the tasks with the time slots, a Custom Task Element is required. You can find below a complete solution which result is demonstrated in the above gif file. Due to the complexity of RadGanttView and its graphical view's internal implementation, there are some UI actions that are forbidden with this approach.
This article just demonstrates a sample approach and it may not cover all possible cases. Resizing the tasks in the graphical view is not allowed. You can use the columns in the text view for editing the start/end time. The links between the tasks are also not supported.
public partial class RadForm1 : Telerik.WinControls.UI.RadForm
{
public RadForm1()
{
InitializeComponent();
this.radGanttView1.ItemElementCreating += radGanttView1_ItemElementCreating;
this.radGanttView1.GanttViewElement.GraphicalViewElement.TimelineRange = TimeRange.Day;
this.radGanttView1.GanttViewElement.GraphicalViewElement.TimelineBehavior = new WorkingDaysGanttViewTimelineBehavior();
this.radGanttView1.GanttViewElement.GraphicalViewElement.OnePixelTime = new TimeSpan(0, 0, 260);
this.SetupGantt();
}
private void radGanttView1_ItemElementCreating(object sender, GanttViewItemElementCreatingEventArgs e)
{
if (e.ViewElement is GanttViewGraphicalViewElement)
{
e.ItemElement = new MyGanttViewTaskItemElement((GanttViewGraphicalViewElement)e.ViewElement);
}
}
private void SetupGantt()
{
this.radGanttView1.GanttViewElement.GraphicalViewElement.TimelineStart = new DateTime(2010, 10, 10);
this.radGanttView1.GanttViewElement.GraphicalViewElement.TimelineEnd = new DateTime(2010, 10, 15);
GanttViewDataItem item1 = new GanttViewDataItem();
item1.Start = new DateTime(2010, 10, 10, 10,0,0);
item1.End = new DateTime(2010, 10, 12,12,0,0,0);
item1.Progress = 30M;
item1.Title = "Summary task.1. title";
GanttViewDataItem subitem11 = new GanttViewDataItem();
subitem11.Start = new DateTime(2010, 10, 10, 12,0,0);
subitem11.End = new DateTime(2010, 10, 10, 15,0,0);
subitem11.Progress = 10M;
subitem11.Title = "Sub-task.1.1 title";
GanttViewDataItem subitem12 = new GanttViewDataItem();
subitem12.Start = new DateTime(2010, 10, 10, 14,0,0);
subitem12.End = new DateTime(2010, 10, 11,13,0,0);
subitem12.Progress = 20M;
subitem12.Title = "Sub-task.1.2 title";
GanttViewDataItem subitem13 = new GanttViewDataItem();
subitem13.Start = new DateTime(2010, 10, 10, 11,0,0);
subitem13.End = new DateTime(2010, 10, 10,16,0,0);
subitem13.Progress = 20M;
subitem13.Title = "Sub-task.1.3 title";
GanttViewDataItem subitem14 = new GanttViewDataItem();
subitem14.Start = new DateTime(2010, 10, 10, 11,0,0);
subitem14.End = new DateTime(2010, 10, 11,16,0,0);
subitem14.Progress = 20M;
subitem14.Title = "Sub-task.1.4 title";
this.radGanttView1.Items.Add(item1);
this.radGanttView1.Items.Add(subitem11);
this.radGanttView1.Items.Add(subitem12);
this.radGanttView1.Items.Add(subitem13);
this.radGanttView1.Items.Add(subitem14);
GanttViewTextViewColumn titleColumn = new GanttViewTextViewColumn("Title");
GanttViewTextViewColumn startColumn = new GanttViewTextViewColumn("Start") { FormatString = "{0: dd.MM.yyyy HH:mm}" };
GanttViewTextViewColumn endColumn = new GanttViewTextViewColumn("End") { FormatString = "{0: dd.MM.yyyy HH:mm}" };
this.radGanttView1.GanttViewElement.Columns.Add(titleColumn);
this.radGanttView1.GanttViewElement.Columns.Add(startColumn);
this.radGanttView1.GanttViewElement.Columns.Add(endColumn);
this.radGanttView1.GanttViewElement.EditorInitialized += GanttViewElement_EditorInitialized;
this.radGanttView1.DragDropService.PreviewDragStart += DragDropService_PreviewDragStart;
}
void DragDropService_PreviewDragStart(object sender, Telerik.WinControls.PreviewDragStartEventArgs e)
{
e.CanStart = false;
}
private void GanttViewElement_EditorInitialized(object sender, GanttViewItemEditorInitializedEventArgs e)
{
GanttViewDateTimeEditor dtEditor = e.Editor as GanttViewDateTimeEditor;
dtEditor.CustomFormat = "dd.MM.yyyy HH:mm";
BaseDateTimeEditorElement el = dtEditor.EditorElement as BaseDateTimeEditorElement;
el.ShowTimePicker = true;
}
}
public class MyGanttViewTaskItemElement : GanttViewTaskItemElement
{
public MyGanttViewTaskItemElement(GanttViewGraphicalViewElement ganttViewBaseViewElement) : base(ganttViewBaseViewElement)
{
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
this.Data.GanttViewElement.ElementTree.Control.Cursor = Cursors.Default;
}
public override void Synchronize()
{
base.Synchronize();
this.LeftLinkHandleElement.PropertyChanged -= LeftLinkHandleElement_PropertyChanged;
this.RightLinkHandleElement.PropertyChanged -= LeftLinkHandleElement_PropertyChanged;
this.LeftLinkHandleElement.Visibility = ElementVisibility.Collapsed;
this.RightLinkHandleElement.Visibility = ElementVisibility.Collapsed;
this.LeftLinkHandleElement.PropertyChanged += LeftLinkHandleElement_PropertyChanged;
this.RightLinkHandleElement.PropertyChanged += LeftLinkHandleElement_PropertyChanged;
}
private void LeftLinkHandleElement_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Visibility")
{
((RadElement)sender).Visibility = ElementVisibility.Collapsed;
}
}
protected override Type ThemeEffectiveType
{
get
{
return typeof(GanttViewTaskItemElement);
}
}
protected override SizeF ArrangeOverride(SizeF finalSize)
{
int startHour = 9;
int endHour = 18;
int workHours = endHour - startHour;
RectangleF clientRect = this.GetClientRectangle(finalSize);
DateTime adjustedStart = this.Data.Start.Date.AddHours(startHour);
DateTime adjustedEnd = this.Data.Start.Date.AddHours(24);
float scale = (float)((this.Data.Start - adjustedStart).TotalSeconds / TimeSpan.FromHours(workHours).TotalSeconds);
if (scale < 0)
{
scale = 0;
}
float x = (float)((this.Data.Start.Date - this.GraphicalViewElement.TimelineBehavior.AdjustedTimelineStart).TotalSeconds /
this.GraphicalViewElement.OnePixelTime.TotalSeconds - this.GraphicalViewElement.HorizontalScrollBarElement.Value);
float x2 = (float)((adjustedEnd - this.GraphicalViewElement.TimelineBehavior.AdjustedTimelineStart).TotalSeconds /
this.GraphicalViewElement.OnePixelTime.TotalSeconds - this.GraphicalViewElement.HorizontalScrollBarElement.Value);
x += (x2 - x) * scale;
float width = this.TaskElement.DesiredSize.Width;
if (this.TaskElement is GanttViewMilestoneElement)
{
width = clientRect.Height;
}
SizeF linkHandleSize = new SizeF(Math.Min(this.GraphicalViewElement.LinksHandlesSize.Width, clientRect.Width),
Math.Min(this.GraphicalViewElement.LinksHandlesSize.Height, clientRect.Height));
if (width <= 0)
{
linkHandleSize = Size.Empty;
}
this.LeftLinkHandleElement.Arrange(new RectangleF(x - linkHandleSize.Width, clientRect.Y + (clientRect.Height - linkHandleSize.Height) / 2,
linkHandleSize.Width, linkHandleSize.Height));
this.TaskElement.Arrange(new RectangleF(x, clientRect.Y, width, clientRect.Height));
this.RightLinkHandleElement.Arrange(new RectangleF(x + width, clientRect.Y + (clientRect.Height - linkHandleSize.Height) / 2,
linkHandleSize.Width, linkHandleSize.Height));
return finalSize;
}
protected override SizeF MeasureOverride(SizeF availableSize)
{
RectangleF clientRect = this.GetClientRectangle(availableSize);
int startHour = 9;
int endHour = 18;
int workHours = endHour - startHour;
int nonWorkHours = 24 - workHours;
float width = 0;
DateTime adjustedStart = this.Data.Start.Date.AddHours(startHour);
DateTime start = this.Data.Start;
if (adjustedStart > start)
{
start = adjustedStart;
}
DateTime adjustedEnd = this.Data.End.Date.AddHours(endHour);
DateTime end = this.Data.End;
if (adjustedEnd < end)
{
end = adjustedEnd;
}
int daysDuration = (end.Date - start.Date).Days ;
if (end.Date.Day != start.Date.Day)
{
width = (float)((end - start).Add(new TimeSpan(-(daysDuration * (nonWorkHours)),0,0)).TotalSeconds /
(float)this.GraphicalViewElement.OnePixelTime.TotalSeconds);
}
else
{
width = (float)((end - start).TotalSeconds / (float)this.GraphicalViewElement.OnePixelTime.TotalSeconds);
}
float scale = workHours / 24f;
width /= scale;
if (this.TaskElement is GanttViewMilestoneElement)
{
this.TaskElement.Measure(new SizeF(clientRect.Height, clientRect.Height));
}
else
{
this.TaskElement.Measure(new SizeF(width, clientRect.Height));
}
SizeF linkHandleSize = new SizeF(Math.Min(this.GraphicalViewElement.LinksHandlesSize.Width,
clientRect.Width), Math.Min(this.GraphicalViewElement.LinksHandlesSize.Height, clientRect.Height));
if (width <= 0)
{
linkHandleSize = Size.Empty;
}
this.LeftLinkHandleElement.Measure(linkHandleSize);
this.RightLinkHandleElement.Measure(linkHandleSize);
return availableSize;
}
}
public class WorkingDaysGanttViewTimelineBehavior : BaseGanttViewTimelineBehavior
{
public override DateTime AdjustedTimelineStart
{
get
{
if (this.GraphicalViewElement.TimelineRange != TimeRange.Day)
return base.AdjustedTimelineStart;
return new DateTime(this.GraphicalViewElement.TimelineStart.Year,
this.GraphicalViewElement.TimelineStart.Month, this.GraphicalViewElement.TimelineStart.Day, 0, 0, 0);
}
}
public override DateTime AdjustedTimelineEnd
{
get
{
if (this.GraphicalViewElement.TimelineRange != TimeRange.Day)
return base.AdjustedTimelineEnd;
return new DateTime(this.GraphicalViewElement.TimelineEnd.Year,
this.GraphicalViewElement.TimelineEnd.Month,
this.GraphicalViewElement.TimelineEnd.Day, 0, 0, 0);
}
}
public override GanttTimelineCellsInfo GetTimelineCellInfoForItem(GanttViewTimelineDataItem item, TimeRange range)
{
if (range != TimeRange.Day)
return base.GetTimelineCellInfoForItem(item, range);
return this.GetTimelineCellInfoForWorkingHoursRange(item);
}
private GanttTimelineCellsInfo GetTimelineCellInfoForWorkingHoursRange(GanttViewTimelineDataItem item)
{
int hoursInDay = 9;
int hoursToAdd = hoursInDay;
if (item.Start < this.AdjustedTimelineStart)
{
if (item.Start.Hour > 0)
hoursToAdd -= item.Start.Hour;
}
if (item.End > this.AdjustedTimelineEnd)
{
if (item.End.Hour < hoursInDay)
hoursToAdd -= (hoursInDay - item.End.Hour);
}
return new GanttTimelineCellsInfo(hoursToAdd) { StartIndex = 9 };
}
public override string GetTimelineBottomElementText(GanttViewTimelineDataItem item, int index)
{
if (item.Range != TimeRange.Day)
return base.GetTimelineBottomElementText(item, index);
string format = this.TimelineLowerItemFormat ?? "{0:HH:mm}";
return string.Format(System.Threading.Thread.CurrentThread.CurrentCulture, format, item.Start.AddHours(index));
}
public override IList<GanttViewTimelineDataItem> BuildTimelineDataItems(TimeRange range)
{
if (range != TimeRange.Day)
return base.BuildTimelineDataItems(range);
return this.BuildTimelineDataItemsForWorkingHoursRange();
}
public IList<GanttViewTimelineDataItem> BuildTimelineDataItemsForWorkingHoursRange()
{
List<GanttViewTimelineDataItem> result = new List<GanttViewTimelineDataItem>();
DateTime adjustedStart = this.AdjustedTimelineStart;
DateTime adjustedEnd = this.AdjustedTimelineEnd;
DateTime currentDate = adjustedStart;
int currentDayNumber = adjustedStart.Day;
int newDayNumber = currentDayNumber;
GanttViewTimelineDataItem item = new GanttViewTimelineDataItem(adjustedStart, adjustedStart.AddHours(1),
this.GraphicalViewElement.TimelineRange, this.GraphicalViewElement.OnePixelTime);
result.Add(item);
while (currentDate < adjustedEnd)
{
item.End = currentDate.AddHours(1);
currentDate = currentDate.AddHours(1);
newDayNumber = currentDate.Day;
if (newDayNumber != currentDayNumber && currentDate.AddHours(1) <= adjustedEnd)
{
currentDayNumber = newDayNumber;
item = new GanttViewTimelineDataItem(currentDate, currentDate, this.GraphicalViewElement.TimelineRange, this.GraphicalViewElement.OnePixelTime);
result.Add(item);
}
}
return result;
}
}