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; } }
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> {
// ✅ 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
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.
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
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:
IncludeWeekendis set totrueDurationUnitis set toDurationUnit.Hour- Each task’s start date is aligned with the start date of its first segment
- Task progress is set to
100
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:
- 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. - I also tested the same scenarios using
ObservableCollectionas 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. - Additionally, the
GanttTimelineSettingslabels 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.
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
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
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
- 7 Replies
- 2 Participants
-
RS Ramesh S
- Jun 2, 2026 04:41 AM UTC
- Jul 9, 2026 01:00 PM UTC