Adding a New Marker to a Gantt Chart in D365

May 13, 2025

By: Jake Zenick

Adding a new marker to a Gantt chart in Dynamics 365 can significantly enhance your project management capabilities by providing clear visual indicators of key dates and milestones. In this guide, we will walk you through the process of integrating a new marker for the "ActionDate" field, which we introduced in part one. This involves creating extensions for the "GanttWrkCtrSetup" form and the "GanttSetup_WrkCtr" class, setting up controls to manage the visibility of the new marker, and adding the necessary code to display the markers on the Gantt chart. By the end of this tutorial, your Gantt chart will be equipped to show whether action dates are delayed or ahead of schedule, offering a more intuitive and informative project overview.

The first thing to do is create an extension of the form “GanttWrkCtrSetup” so we can add a control to show or hide the ActionDate marker as shown below:

How to add a new marker to a gantt chart

We will now create a code extension of the GanttSetup_WrkCtr class to store off the selection for our new “ShowActionDates” control

[ExtensionOf(classStr(GanttSetup_WrkCtr))]
final class GanttSetup_WrkCtr_Extension
{
private NoYes showActionDate;

public NoYes parmShowActionDate(NoYes _showActionDate = showActionDate)
{
if (showActionDate != _showActionDate)
{
mustReloadData = NoYes::Yes;
showActionDate = _showActionDate;
}

return showActionDate;
}
}

With the control class ready we can now create a code extension on the GanttWrkCtrSetup form to actually set the selected value

[ExtensionOf(formStr(GanttWrkCtrSetup))]
final class GanttWrkCtrSetup_Extension
{
protected void getGeneralValuesFromForm()
{
next getGeneralValuesFromForm();

ganttSetup.parmShowActionDate(ShowActionDates.value());
}

protected void getGeneralValuesFromClass()
{
next getGeneralValuesFromClass();

ShowActionDates.value(ganttSetup.parmShowActionDate());
}
}

Finally we will be adding code to our GanttControlVisualization_WrkCtr class that will actually add the new markers.

Start by defining what image to display for the marker – in our case we will be using two images to show visually if the action date is a delayed or ahead of schedule. A full list of the images available can be found here.

[ExtensionOf(classStr(GanttControlVisualization_WrkCtr))]
final class GanttControlVisualization_WrkCtr_Extension
{
private str actionDateDelay = 'First';
private str actionDateAdvance = 'Last';
}

Depending on if the gantt chart is in resource or order view; the action date will be considered as a group header or part of a group, respectively.

For our header case we will add a chain of command extension on the method “setActivityMarkersAndSymbols” that will check our previously setup parameters and add the appropriate milestone markers.

protected void setActivityMarkersAndSymbols(GanttControlTaskActivitySchedulable _activity, GanttTmpWrkCtrJob _ganttTmpWrkCtrJob)
{
next setActivityMarkersAndSymbols(_activity, _ganttTmpWrkCtrJob);

GanttSetup_WrkCtr localGanttSetup = this.parmGanttClient().parmGanttSetup() as GanttSetup_WrkCtr;
GanttView_WrkCtr ganttView = ganttClient.parmGanttView() as GanttView_WrkCtr;

if (localGanttSetup.parmShowActionDate() && ganttView.parmCurrentView() == GanttViewWrkCtr::ResourceView)
{
GanttColor_WrkCtr ganttColor = ganttClient.parmGanttColor() as GanttColor_WrkCtr;

if (_activity.parmMilestoneMarkers().empty())
{
// Clear the existing markers in case of re-assignment
_activity.parmMilestoneMarkers(new List(Types::Class));
}

_activity.parmSymbolName('');

if (_ganttTmpWrkCtrJob.ActionDate)
{
utcdatetime actionDateTime = DateTimeUtil::newDateTime(_ganttTmpWrkCtrJob.ActionDate, 14400, DateTimeUtil::getUserPreferredTimeZone());

GanttControlMilestoneMarker marker = GanttControlMilestoneMarker::newParameters(
'ADateMark' + _activity.parmId(),
actionDateTime,
"@SYS22951",
_ganttTmpWrkCtrJob.SchedFromDateTime < actionDateTime ? actionDateDelay : actionDateAdvance,
ganttColor.parmActiveNodeColor());

_activity.parmMilestoneMarkers().addEnd(marker);
}
}
}

For the group case we will be adding a COC extension to the method “getActivities” and a new method called “addActionDateGroupMarkers”.

addActionDateGroupMarkers is what will actually add our new marker as show below:

private void addActionDateGroupMarkers(GanttTmpWrkCtrJob _ganttTmpWrkCtrJob, ListEnumerator _groupFieldEnumerator)
{
GanttColor_WrkCtr ganttColor = ganttClient.parmGanttColor() as GanttColor_WrkCtr;
str summaryPath = strFmt('#%1', _ganttTmpWrkCtrJob.(_groupFieldEnumerator.current().parmFieldId()));
str activityId;

MapIterator summaryActivityPatchMapIterator = new MapIterator(summaryActivityPathMap);

while (summaryActivityPatchMapIterator.more())
{
if (summaryActivityPatchMapIterator.value() == summaryPath)
{
activityId = summaryActivityPatchMapIterator.key();
break;
}

summaryActivityPatchMapIterator.next();
}

if (activityId)
{

GanttControlSummaryActivity groupActivity = activityMap.lookup(activityId);

utcdatetime actionDateTime = DateTimeUtil::newDateTime(_ganttTmpWrkCtrJob.ActionDate, 14400, DateTimeUtil::getUserPreferredTimeZone());

GanttControlMilestoneMarker milestoneMarker = GanttControlMilestoneMarker::newParameters(
strFmt('%1ADateMark', groupActivity.parmId()),
actionDateTime,
"@SYS22951",
_ganttTmpWrkCtrJob.SchedFromDateTime < actionDateTime ? actionDateDelay : actionDateAdvance,
ganttColor.parmActiveNodeColor());

groupActivity.parmMilestoneMarkers().addEnd(milestoneMarker);
}
}

The COC method will allow us to call addActionDateGroupMarkers to add the new markers as it generates the activities to be shown on the gantt chart. Due to COC limitations we will just be looping through the list of activities from the base call to getActivities and inserting our new markers as necessary.

public List getActivities()
{
List activities = next getActivities();

GanttSetup_WrkCtr localGanttSetup = this.parmGanttClient().parmGanttSetup() as GanttSetup_WrkCtr;
GanttView_WrkCtr ganttView = ganttClient.parmGanttView() as GanttView_WrkCtr;
ListEnumerator groupFieldEnumerator = groupFields.getEnumerator();

groupFieldEnumerator.reset();

if (ganttView.parmCurrentView() == GanttViewWrkCtr::OrderView
&& localGanttSetup.parmShowActionDate()
&& groupFieldEnumerator.moveNext())
{
Query query = new Query();
QueryBuildDataSource qbds = query.addDataSource(tableNum(GanttTmpWrkCtrJob));

qbds.addRange(fieldNum(GanttTmpWrkCtrJob, SchedFromDate)).value(SysQuery::range(null, ganttClient.parmToDate()));
qbds.addRange(fieldNum(GanttTmpWrkCtrJob, SchedToDate)).value(SysQuery::range(ganttClient.parmFromDate(), null));
qbds.addRange(fieldNum(GanttTmpWrkCtrJob, IsDisplayed)).value(SysQuery::value(NoYes::Yes));

QueryRun queryRun = new QueryRun(query);
GanttData_WrkCtr ganttData_WrkCtr = ganttClient.parmGanttData() as GanttData_WrkCtr;
queryRun.setRecord(ganttData_WrkCtr.parmGanttTableWrkCtrJob().parmTableBuffer());

while (queryRun.next())
{
GanttTmpWrkCtrJob ganttTmpWrkCtrJob = queryRun.get(tableNum(GanttTmpWrkCtrJob));

if (ganttTmpWrkCtrJob.ActionDate)
{
this.addActionDateGroupMarkers(ganttTmpWrkCtrJob, groupFieldEnumerator);
}
}
}

return activities;
}

Now our gantt chart will include markers for action dates, quickly showing the user if there is a delay or if production is ahead of schedule.

By following these steps, your Gantt chart will now include markers for action dates, providing a clear visual indication of delays or ahead-of-schedule production. This enhancement will greatly improve the user experience by offering quick and easy insights into the project timeline.

Interested in learning more about how Strabo Partners can be your implementation partner? Contact us today!