If you have a timescale in a Gantt chart spanning several years, but still want to be able to see every single day or even the hours in a day, you will soon find that the horizontal scrollbar becomes very sensitive. The smallest movement leads the timescale to shift several days into one or the other direction, even if smaller intervals are required as you look for a specific date and time. This behaviour can result in some unwanted user experiences for the planner working with your scheduling application.
This blog post belongs to a series of Gantt best practices that we regularly publish relating to our Gantt control VARCHART XGantt. This trick describes how to extend the mouse functionality to also use the timescale as scrollbar.
Usually you expand or collapse the timescale by mouse movement. How about if you use the fluid movement of the mouse interaction to drag the timescale in addition? You can utilize the scrollbar, of course, but when working with a long planning horizon the movement becomes jumpy and you can have problems to place the Gantt chart at a sprecific date and time.
Below are some event implementations and functions for a mode that is activated if you push a shift-key and move the mouse in the timescale. Now you can drag and drop the timescale to the date you want to see.
Watch a timescale used as scrollbar
Events that are used in this code
vcGantt1.MouseMove += vcGantt1_MouseMove;
vcGantt1.MouseDown += vcGantt1_MouseDown;
vcGantt1.MouseUp += vcGantt1_MouseUp;
vcGantt1.KeyUp += vcGantt1_KeyUp;
vcGantt1.KeyDown += vcGantt1_KeyDown;
Code Sample
private bool timeScaleDraggingMode;
private DateTime dateUnderCursor;
private void vcGantt1_MouseDown(object sender, MouseEventArgs e)
{
object hitObject = null;
VcObjectType hitObjectType = VcObjectType.vcObjTypeNone;
vcGantt1.IdentifyObjectAt(e.X, e.Y, ref hitObject, ref hitObjectType);
//The timeScaleDraggingMode will only be set to true if the timescale rescaling is disabled and you drag and drop with your left mousebutton on the timescale
if (hitObjectType == VcObjectType.vcObjTypeTimeScale && e.Button == MouseButtons.Left && vcGantt1.TimeScaleRescalingAllowed == false)
{
timeScaleDraggingMode = true;
//This date will be below the mousecursor for every movement while dragging
dateUnderCursor = vcGantt1.GetDate(e.X);
}
}
private void vcGantt1_MouseMove(object sender, MouseEventArgs e)
{
if (timeScaleDraggingMode)
{
ScrollToMousePosition(vcGantt1, e, dateUnderCursor);
}
}
private void vcGantt1_MouseUp(object sender, MouseEventArgs e)
{
if (timeScaleDraggingMode)
{
timeScaleDraggingMode = false;
}
}
private void ScrollToMousePosition(VcGantt gantt, MouseEventArgs e, DateTime dateUnderCursor)
{
// Save the original timeunit set in the XGantt control
VcTimeUnit timeUnit = gantt.TimeUnit;
gantt.TimeUnit = VcTimeUnit.vcSecond; //Use seconds as timeunit for highest precision during mouse movement
//Get the x Location of the XGantt Timescale
int xTimeScalePosition = 0;
int dummy = 0;
gantt.GetViewComponentSize(VcComponentType.vcTimeScaleComponent, ref xTimeScalePosition, ref dummy, ref dummy, ref dummy);
const int SplitterWidth = 4;
int mousePositionInTimeScale = e.X - xTimeScalePosition - SplitterWidth; //Calculate the mouseposition relative to the x location of the timescale
VcSection section = gantt.TimeScaleCollection.Active.get_Section(0);
//This calculates the number of seconds between the most left date and time showing on the timescale and the date and time that are supposed to be under the mouse cursor
double gapAsNoOfTimeUnits = (gantt.ConvertDistance(VcDistanceConversionType.vcXPixelsToCentiMillimeters, mousePositionInTimeScale)
/ (section.UnitWidthEx)) * GetTimeUnitFactor(gantt.TimeUnit) / GetTimeUnitFactor(section.TimeUnit);
//If the non work intervals were collapsed we need to use the active calendar
if (section.NonWorkIntervalsCollapsed)
{
gantt.ScrollToDate(gantt.CalendarCollection.Active.AddDuration(dateUnderCursor,
(int)-gapAsNoOfTimeUnits), VcHorizontalAlignment.vcLeftAligned, 0);
}
else
{
gantt.ScrollToDate(dateUnderCursor.AddSeconds(-gapAsNoOfTimeUnits), VcHorizontalAlignment.vcLeftAligned, 0);
}
gantt.TimeUnit = timeUnit;
}
//Returns the number of given units (day, hour, minute, second) in one day
private double GetTimeUnitFactor(VcTimeUnit timeUnit)
{
switch (timeUnit)
{
case VcTimeUnit.vcDay:
return 1;
case VcTimeUnit.vcHour:
return 24;
case VcTimeUnit.vcMinute:
return 24 * 60;
case VcTimeUnit.vcSecond:
return 24 * 60 * 60;
default:
return 0;
}
}
//As soon as you press the shift keys you can move the time scale via drag and drop.
private void vcGantt1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.ShiftKey)
{
//If movement should be standard for drag and drop on the timescale change this to true
//This will enable you to rescale the timescale while pressing shift
vcGantt1.TimeScaleRescalingAllowed = false;
}
}
private void vcGantt1_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.ShiftKey)
{
//If movement should be standard for drag and drop on the timescale change this to false
vcGantt1.TimeScaleRescalingAllowed = true;
}
}