Programatically altering task and segments does not re-render SfGantt

Hi,

Can we programmatically modify tasks and segments in SfGantt? For example, can we swap task rows, insert a new task, or move a segment from one task to another?

I tried swapping two tasks using the code below, but it looks like the SfGantt control does not re-render the modified data.

Thanks,

Ramesh S

@page "/"
@using Syncfusion.Blazor.Gantt
@using Syncfusion.Blazor.Buttons

@rendermode InteractiveWebAssembly

<h3>Syncfusion SfGantt – Large Segmented Dataset</h3>
<p>
	150 tasks · ~1,250 segments · No subtasks...
	Timeline: Day / Hour · Project span: 15 days
</p>
<div>
	<SfButton CssClass="action-secondary"  @onclick="SwitchClick">Switch</SfButton>
</div>
<SfGantt TValue="TaskData" @ref="gantt"
		 DataSource="@taskCollection"
		 Height="670px"
		 Width="100%"
		 RowHeight="40"
		 TaskbarHeight="20"
		 ProjectStartDate="@projectStart"
		 ProjectEndDate="@projectEnd"
		 DurationUnit="DurationUnit.Hour"
		 IncludeWeekend="true"
		 EnableRowVirtualization="true"
		 EnableTimelineVirtualization="true">

	<GanttTaskFields Id="TaskID"
					 Name="TaskName"
					 StartDate="StartDate"
					 EndDate="EndDate"
					 Progress="Progress">
	</GanttTaskFields>

	<!-- Segmented Tasks -->
	<GanttSegmentFields TValue="TaskData"
						TSegments="SegmentModel"
						PrimaryKey="Id"
						ForeignKey="TaskID"
						StartDate="SegmentStartDate"
						EndDate="SegmentEndDate"
						DataSource="@segmentCollection">
	</GanttSegmentFields>

	<!-- Timeline -->
	<GanttTimelineSettings TimelineUnitSize="60">
		<GanttTopTierSettings Unit="TimelineViewMode.Day" Format="ddd, MMM dd" />
		<GanttBottomTierSettings Unit="TimelineViewMode.Hour" Format="HH:mm" />
	</GanttTimelineSettings>

	<GanttDayWorkingTimeCollection>
		<GanttDayWorkingTime From="0" To="24" />
	</GanttDayWorkingTimeCollection>

	<GanttColumns>
		<GanttColumn Field="TaskID" HeaderText="ID" Width="90" IsPrimaryKey="true" />
		<GanttColumn Field="TaskName" HeaderText="Task Name" Width="260" />
		<GanttColumn Field="StartDate" HeaderText="Overall Start" Width="170" Format="M/d/yyyy HH:mm" />
		<GanttColumn Field="EndDate" HeaderText="Overall End" Width="170" Format="M/d/yyyy HH:mm" />
		<GanttColumn Field="Progress" HeaderText="Progress (%)" Width="120" />
	</GanttColumns>

	<GanttTemplates TValue="TaskData">
		<TaskbarTemplate>
			@{
				var task = (context as TaskData);
				if (task == null)
				{
					return;
				}
				var taskModel = gantt.GetRowTaskModel(task);
				List<GanttSegmentData> segments = taskModel.Segments;
				@if (segments != null && segments.Count() > 1)
				{
					foreach (var segment in segments)
					{
						<div class="e-gantt-child-taskbar-inner-div e-gantt-child-taskbar e-segmented-taskbar" style=@("height:37px;position: absolute;left:" + segment.Left + "px; width:" + segment.Width + "px;") tabindex=-1 data-segment-index="@(segment.SegmentIndex)">
							<div class="e-taskbar-left-resizer e-icon" style="margin-top: 5px; left:2px">
								@($"{segment.StartDate}-{segment.EndDate}")
							</div>

						</div>
					}
				}
				else
				{
					<div class="e-gantt-child-taskbar e-gantt-child-taskbar-inner-div" style="height:37px;" tabindex=-1>
						<div class="e-gantt-child-progressbar-inner-div e-gantt-child-progressbar" style="height:24px;width:@(taskModel.ProgressWidth + "px");text-align: right;border-radius: 0px;">
						</div>
					</div>
				}
			}
		</TaskbarTemplate>
	</GanttTemplates>

	<GanttSelectionSettings Mode="Syncfusion.Blazor.Grids.SelectionMode.Row"
							Type="Syncfusion.Blazor.Grids.SelectionType.Multiple">
	</GanttSelectionSettings>

	<GanttEditSettings AllowEditing="true"
					   AllowAdding="true"
					   AllowDeleting="true"
					   AllowTaskbarEditing="true"
					   Mode="Syncfusion.Blazor.Gantt.EditMode.Dialog">
	</GanttEditSettings>

</SfGantt>

@code {
	private SfGantt<TaskData> gantt;
	private DateTime projectStart = new DateTime(2026, 4, 6, 0, 0, 0);
	private DateTime projectEnd = new DateTime(2026, 4, 21, 23, 59, 59);

	private List<TaskData> taskCollection = new();
	private List<SegmentModel> segmentCollection = new();

	protected override void OnInitialized()
	{
		GenerateLargeDataset();
	}

	// ===================== MODELS =====================

	public class TaskData
	{
		public int TaskID { get; set; }
		public string TaskName { get; set; } = string.Empty;
		public DateTime StartDate { get; set; }
		public DateTime EndDate { get; set; }
		public int Progress { get; set; }
		public int DisplayOrder { get; set; }

		public TaskData()
		{

		}

		public TaskData(TaskData other)
		{
			TaskID = other.TaskID;
			TaskName = other.TaskName;
			StartDate = other.StartDate;
			EndDate = other.EndDate;
			Progress = other.Progress;
			DisplayOrder = other.DisplayOrder;
		}
	}

	public class SegmentModel
	{
		public int Id { get; set; }
		public int TaskID { get; set; }
		public DateTime SegmentStartDate { get; set; }
		public DateTime SegmentEndDate { get; set; }
	}

	// ===================== DATA GENERATOR =====================

	private void GenerateLargeDataset()
	{
		var random = new Random();
		int segmentId = 1;

		for (int taskId = 1; taskId <= 150; taskId++)
		{
			int segmentCount = random.Next(5, 11); // 5–10 segments

			var taskSegments = new List<SegmentModel>();

			DateTime cursor = projectStart.AddDays(random.Next(0, 5))
													.AddHours(random.Next(5, 12));

			for (int i = 0; i < segmentCount; i++)
			{
				int durationHours = random.Next(2, 8);

				var segmentStart = cursor;
				var segmentEnd = segmentStart.AddHours(durationHours);

				taskSegments.Add(new SegmentModel
				{
					Id = segmentId++,
					TaskID = taskId,
					SegmentStartDate = segmentStart,
					SegmentEndDate = segmentEnd
				});

				// Gap before next segment
				cursor = segmentEnd.AddHours(random.Next(6, 36));
				if (cursor > projectEnd)
					break;
			}

			if (!taskSegments.Any())
				continue;

			segmentCollection.AddRange(taskSegments);

			taskCollection.Add(new TaskData
			{
				TaskID = taskId,
				TaskName = $"Task {taskId:000}",
				StartDate = taskSegments.Min(s => s.SegmentStartDate),
				EndDate = taskSegments.Max(s => s.SegmentEndDate),
				Progress = 100,   //random.Next(30, 101),
				DisplayOrder = taskId
			});
		}
	}

	private async Task SwitchClick()
	{
		Swap(taskCollection, 9, 10);
		await gantt.RefreshAsync();
		//StateHasChanged();
	}

	public static void Swap(List<TaskData> list, int indexA, int indexB)
	{
		if (list == null)
			throw new ArgumentNullException(nameof(list));

		if (indexA < 0 || indexA >= list.Count)
			throw new ArgumentOutOfRangeException(nameof(indexA));

		if (indexB < 0 || indexB >= list.Count)
			throw new ArgumentOutOfRangeException(nameof(indexB));

		if (indexA == indexB)
			return;

		TaskData temp = list[indexA];
		list[indexA] = list[indexB];
		list[indexB] = temp;
	
	}
}

7 Replies

AG Ajithkumar Gopalakrishnan Syncfusion Team June 3, 2026 06:58 AM UTC

Hi Ramesh,

Greetings from Syncfusion Support,

Based on your query, we understand that when swapping the position of records (e.g., moving a record from one position to another), the changes are not reflected in the UI of the Gantt Chart. On your end, taskCollection is bound as the data source to the Gantt Chart. When the button is clicked, the records are swapped within the same collection reference, and RefreshAsync() is called. However, the UI is not updated because the data source reference remains unchanged.

The purpose of the RefreshAsync() method is to refresh the UI rendering. It does not detect internal modifications when the same collection reference is reused. Therefore, since the data source reference does not change, the UI does not reflect the updated order.

To achieve your requirement, assign a new reference to the collection after modifying it. When a new reference is assigned, the Gantt Chart automatically detects the change and updates the UI. In this case, there is no need to call RefreshAsync().

</p><div><SfButton CssClass="action-secondary"  @onclick="SwitchClick">Switch</SfButton></div>

<
SfGantt TValue="TaskData" @ref="gantt" DataSource="@taskCollection" >

       ...

</SfGantt>

private
async Task SwitchClick()

{

 

       // Create new list reference

       var newList = taskCollection.ToList();

 

       // Swap inside new list

       Swap(newList, 9, 10);

 

       // Assign back (VERY IMPORTANT)

       taskCollection = newList;

}


Additionally, during the button click, only the taskCollection is being updated. As a result, the Gantt Chart renders only the basic taskbars because the segments collection is not updated.

We also noticed that the taskbar template is not implemented correctly on your end, which causes misalignment between the taskbar and progress bar, as shown in your attached image.

Before image reference:

var ProgressWidth = (taskModel.Width * task.Progress) / 100;

@if (segments != null && segments.Count() > 1)

{

       …

}

else

{

                          <div class="e-gantt-parent-taskbar e-gantt-child-taskbar-inner-div" style="height:24px;" tabindex=-1>

                                 <div class="e-gantt-child-progressbar-inner-div e-gantt-child-progressbar" style="height:24px;width:@(ProgressWidth + "px");text-align: right;border-radius: 0px;">

                                 </div>

                          </div>

}


After image reference:



Sample link: https://blazorplayground.syncfusion.com/embed/hNVRZdjArUHjabPX?appbar=true&editor=true&result=true&errorlist=true&theme=fluent2

For more details, please refer to the following User Guide documentation:

https://blazor.syncfusion.com/documentation/gantt-chart/taskbar#taskbar-template

Please let us know if you need any further assistance.

Regards,
Ajithkumar G



RS Ramesh S June 3, 2026 08:53 AM UTC

Hi Ajithkumar,

Thanks for your response.


As per the following Syncfusion Blazor SfGantt documentation, the Gantt chart supports automatic UI updates when the data source implements `INotifyCollectionChanged` and `INotifyPropertyChanged`.

https://blazor.syncfusion.com/documentation/gantt-chart/data-binding

"Observable collection and INotifyPropertyChanged

The Gantt chart supports to automatically update data based on INotifyCollectionChanged and INotifyPropertyChanged interface.

Observable collection

To handle dynamic changes in the data source, the Gantt Chart supports binding to an ObservableCollection. This collection implements the INotifyCollectionChanged interface, which automatically notifies the UI when items are added, removed, moved, or cleared."

The documentation mentions that binding the Gantt chart to an `ObservableCollection` should notify the UI automatically when items are added, removed, moved, or cleared.

I also tried the sample provided in the documentation. However, after swapping rows, it looks like the task segments are rendered with different or incorrect ranges.

My requirement is very simple: I only need to swap the task rows, while keeping the segments rendered correctly for their respective task rows. The segments should always be rendered based on their actual start and end date/time values pertaining to the tasks.



AG Ajithkumar Gopalakrishnan Syncfusion Team June 8, 2026 05:54 AM UTC

Hi Ramesh,

Based on your query, we understand that you are referring to the use of an observable collection and expecting the Gantt Chart UI to update automatically when the collection changes. We would like to clarify the behavior of the Gantt Chart in this scenario.


In your previous query, you reported that the record position was updated, but the UI did not reflect the changes. At that time, we explained the current dataSource handling mechanism of the Gantt Chart. In your implementation, you were using standard data binding with a self-referential data structure, where updates to the data do not automatically trigger UI refresh unless the entire dataSource is reassigned.

In your current query, you have referenced the observable collection approach. When an observable collection is used, UI updates can occur dynamically as the underlying collection changes. However, this behavior differs from the previous self-referential binding approach you used.


Observable collection : https://blazor.syncfusion.com/documentation/gantt-chart/data-binding#observable-collection-and-inotifypropertychanged

Self-referential data structure : https://blazor.syncfusion.com/documentation/gantt-chart/data-binding#self-referential-data-structure

Additionally, the workaround we previously provided involves dynamically updating the Gantt Chart’s dataSource to reflect changes. However, we observed that when working with segmented tasks (task segments), the data is currently rendered as a normal taskbar instead of a segmented taskbar. We have logged the reported issue, “Segment collection is not updated when the task collection is dynamically changed” as a bug. Our team is actively working on a fix, and we plan to include it in the June 23, 2026 weekly release.


You can track the progress of the resolution by visiting the feedback link provided below:


Feedback link: https://www.syncfusion.com/feedback/74402/segment-collection-is-not-updated-when-the-task-collection-is-dynamically-changed


Disclaimer: Inclusion of this solution in the weekly release may change due to other factors including but not limited to QA checks and works reprioritization.

After the bug fix, we have provided a sample where the dataSource is dynamically updated when the button is clicked, and the UI is updated correctly with segment rendering on our end.

Regards
Ajithkumar G



RS Ramesh S June 8, 2026 06:55 AM UTC

Hi Ajithkumar,


In another thread, I asked whether segments in SfGantt can be rendered strictly based on SegmentData, without any internal calculations. I was able to achieve this by using the following conditions:

  • IncludeWeekend is set to true
  • DurationUnit is set to DurationUnit.Hour
  • Each task’s start date is aligned with the start date of its first segment
  • Task progress is set to 100

Thread: https://www.syncfusion.com/forums/198265/split-task-sement-in-sfgantt-is-not-drawn-for-the-exact-date-time

However, when swapping task rows, modifying an existing segment, or adding a new segment that falls within the start and end dates of the first and last segments, SfGantt behaves unexpectedly.

The following issues were observed:

  1. When List<T> was used as the data source for both tasks and segments,  the whole segment area is rendered in white even after switching task rows and reassigning new data sources.
  2. I also tested the same scenarios using ObservableCollection as the data source for both tasks and segments. In this case, after switching task rows, the entire Gantt chart was rendered with incorrect segment ranges and colors.
  3. Additionally, the GanttTimelineSettings labels were modified unexpectedly, and different timing values were displayed after the row switch.

I tested these scenarios with bothEnableRowVirtualization and EnableTimelineVirtualization enabled and disabled. I also tested them using <GanttTemplates>.

I hope these issues will be addressed in the upcoming bug fix release.


Thanks.



AG Ajithkumar Gopalakrishnan Syncfusion Team June 9, 2026 12:42 PM UTC

Hi Ramesh,

We have previously logged a bug titled “Segment collection is not updated when the task collection is dynamically changed.” In this scenario, during the initial load, both the taskData and segment collection are correctly updated, and the UI is rendered accordingly. However, when the taskData collection is dynamically changed, the segment data is not updated properly, and the taskbars are rendered as normal (non-segmented). This specific case has already been addressed and covered on our end, and we have shared updates regarding this in our earlier responses.

Currently, multiple issues have been reported related to the Gantt Chart. To better analyze and assist you, we request you to provide the following:

  • A clear explanation of your requirement, including the exact scenario in which the issue occurs on your end
  • A reproducible sample illustrating the issue
  • A video demo showing the problem clearly

Once we receive the sample and video demonstration, we will review the issue in detail and provide an accurate solution.

Regards,
Ajithkumar G



AG Ajithkumar Gopalakrishnan Syncfusion Team June 23, 2026 12:30 PM UTC

Hi Ramesh,

Sorry for the inconvenience caused.

As we need to ensure proper coverage of all related use cases, we were unable to include the fix for the issue, “Segment collection is not updated when the task collection is dynamically changed,” in the weekly release scheduled for June 23, 2026. We are planning to include the fix in the first patch release after the Volume 2 main release, which is tentatively scheduled for the end of June 2026.

We sincerely apologize for any inconvenience this may cause and appreciate your understanding and patience.

Regards,
Ajithkumar G



AG Ajithkumar Gopalakrishnan Syncfusion Team July 9, 2026 01:00 PM UTC

Hi Ramesh,

We appreciate your patience.

We are glad to announce that we have included the fix for the issue “Segment collection is not updated when task collection is dynamically changed ” in our 34.1.30 release. So please upgrade to our latest version of the Syncfusion package to resolve the reported issue.

Root Cause: When the DataSource property changes, the GanttCollection is reset and a new empty GanttTaskItem is created. The ProcessSegmentCollection method only rebuilt segments during initial load (onLoad=true), so after a DataSource refresh it returned the empty segments instead of processing the provided segmentCollection.

Solution: Added a new IsDynamicDataSourceUpdate flag and modified the condition in ProcessSegmentCollection, so segments are rebuilt from the segmentCollection during dynamic data source updates as well.

Sample link: Attached as zip file.

Release notes: https://blazor.syncfusion.com/documentation/release-notes/34.1.30?type=all#gantt-chart

We thank you for your support and appreciate your patience in waiting for this release. Please get back to us if you need any further assistance.

Regards,
Ajithkumar G


Loader.
Up arrow icon