Inline Edit Row in Blazor TreeList
- 16 minutes to read
In EditRow mode, the TreeList component displays in-place editors in all data cells of the edited row. Users can click command buttons to create, modify, and delete TreeList rows.

The following keyboard shortcuts speed up row editing in EditRow mode:
- Enter
- Validates input and saves changes.
- Esc
- Discards all changes made in the row and cancels row editing.
- Tab and Shift+Tab
- Move focus between focusable content in TreeList cells (editors, buttons, cells in display mode).
Refer to the following help topic for the full list of shortcut keys: Keyboard Support in Blazor TreeList.
Enable Editing
Follow the steps below to allow users to create, modify, and delete rows in EditRow mode.
- Declare a DxTreeListCommandColumn object in the Columns template to display the command column.
- If your data object has a primary key, assign it to the KeyFieldName property. Otherwise, the TreeList uses standard .NET value equality comparison to identify data items.
- Handle the CustomizeEditModel event to initialize an edit model for new data rows.
- Handle the EditModelSaving and DataItemDeleting events to save changes and reload the TreeList data.
- (Optional) Use the EditNewRootRowPosition property to display the new item row or change the position of the edit row displayed for new root nodes.
When users start editing a row, the TreeList creates a clone of the edited data item, called an edit model. During edit operations, the TreeList applies user changes only to the edit model and keeps the data item unchanged until the EditModelSaving or DataItemDeleting event fires. Refer to the following topic for additional information: Edit Model in Blazor TreeList.
The following code sample allows users to edit TreeList data in the inline edit row:
@inject EmployeeTaskService EmployeeTaskService
<DxTreeList Data="TreeListData"
KeyFieldName="Id"
ParentKeyFieldName="ParentId"
EditModelSaving="TreeList_EditModelSaving"
DataItemDeleting="TreeList_DataItemDeleting"
CustomizeEditModel="TreeList_CustomizeEditModel">
<Columns>
<DxTreeListCommandColumn />
<DxTreeListDataColumn FieldName="Name" Caption="Task" />
<DxTreeListDataColumn FieldName="EmployeeName" />
<DxTreeListDataColumn FieldName="StartDate" />
<DxTreeListDataColumn FieldName="DueDate" />
</Columns>
</DxTreeList>
@code {
List<EmployeeTask> TreeListData { get; set; }
protected override void OnInitialized() {
TreeListData = EmployeeTaskService.GenerateData();
}
void TreeList_CustomizeEditModel(TreeListCustomizeEditModelEventArgs e) {
if(e.IsNew) {
var newTask = (EmployeeTask)e.EditModel;
newTask.Id = TreeListData.Max(x => x.Id) + 1;
if(e.ParentDataItem != null)
newTask.ParentId = ((EmployeeTask)e.ParentDataItem).Id;
}
}
void TreeList_EditModelSaving(TreeListEditModelSavingEventArgs e) {
if(e.IsNew)
TreeListData.Add((EmployeeTask)e.EditModel);
else
e.CopyChangesToDataItem();
}
void TreeList_DataItemDeleting(TreeListDataItemDeletingEventArgs e) {
TreeListData.Remove((EmployeeTask)e.DataItem);
}
}
Related API
This section contains comprehensive API references for editing.
| DxTreeList API Member | Type | Description |
|---|---|---|
| CustomValidators | Property | Allows you to declare custom validator components. |
| DataColumnCellEditTemplate | Property | Allows you to replace automatically generated editors with custom content in all edit cells displayed for data columns. |
| EditMode | Property | Specifies how users can edit TreeList data. |
| EditNewRootRowPosition | Property | Specifies the position of UI elements used to create new root nodes. |
| ValidationEnabled | Property | Specifies whether the TreeList validates user input. |
| CancelEditAsync() | Method | Cancels row editing and discards changes. |
| GetColumnEditSettings<T>(String) | Method | Returns editor settings of the column bound to the specified data source field. |
| GetEditContext() | Method | Returns the edit context. |
| IsEditing() | Method | Returns whether the TreeList is being edited. |
| IsEditingNewRow() | Method | Returns whether a new TreeList row is being edited. |
| IsEditingRow(Int32) | Method | Returns whether the specified TreeList row is being edited. |
| SaveChangesAsync() | Method | Triggers validation and raises the EditModelSaving event if validation succeeds. The method immediately raises this event if validation is disabled. |
| ShowRowDeleteConfirmation(Int32) | Method | Displays the delete confirmation dialog for the specified row. If a user confirms the operation, the method raises the DataItemDeleting event. |
| StartEditNewRowAsync(String) | Method | Starts editing a new root node. |
| StartEditNewRowAsync(Int32, String) | Method | Starts editing a new child node of the row with the specified visible index. |
| StartEditRowAsync(Int32, String) | Method | Starts editing the row with the specified visible index. |
| CustomizeDataRowEditor | Event | Allows you to customize a cell editor in a data row. |
| CustomizeEditModel | Event | Allows you to customize an automatically generated edit model or create a custom edit model. |
| DataItemDeleting | Event | Fires after a user confirms the delete operation in the delete confirmation dialog. |
| EditCanceling | Event | Fires before the TreeList cancels the edit operation and discards changes. |
| EditModelSaving | Event | Fires if validation succeeds after a user saves changes or you call the SaveChangesAsync() method. |
| EditStart | Event | Fires before the TreeList starts editing a row. |
| DxTreeListCommandColumn API Member | Type | Description |
|---|---|---|
| CancelButtonVisible | Property | Specifies whether the command column displays the Cancel button in EditRow or EditCell edit mode. |
| CellDisplayTemplate | Property | Specifies a template used to display command column cells in display mode. |
| CellEditTemplate | Property | Specifies a template used to display the command column’s edit cell. |
| DeleteButtonVisible | Property | Specifies whether the command column displays Delete buttons. |
| DisplayMode | Property | Specifies whether command buttons display icons, captions, or both. |
| EditButtonVisible | Property | Specifies whether the command column displays Edit buttons. |
| HeaderTemplate | Property | Specifies a template used to display the command column header. |
| NewButtonVisible | Property | Specifies whether the command column displays New buttons. |
| SaveButtonVisible | Property | Specifies whether the command column displays the Save button in EditRow or EditCell edit mode. |
| DxTreeListDataColumn API Member | Type | Description |
|---|---|---|
| CellEditTemplate | Property | Allows you to replace an automatically generated editor with custom content in the column’s edit cell. |
| DataRowEditorVisible | Property | Specifies whether to render the editor associated with this column in the column edit cell, edit form, or pop-up edit form. |
| EditSettings | Property | Allows you to customize the editor associated with this column. |
| ReadOnly | Property | Specifies whether a user can change the column editor value when the TreeList is in edit mode. |
Task-Based Examples
This section includes code samples that demonstrate row-editing functionality.
Data Editing With Custom Command Buttons
You can also manage edit operations without a command column. Instead of built-in command buttons, create custom buttons in the TreeList toolbar or another location.
Use the following methods to implement external command buttons:
- StartEditNewRowAsync
- Starts editing a new row.
- StartEditRowAsync
- Starts editing the specified row.
- CancelEditAsync
- Cancels row editing and discards changes.
- SaveChangesAsync
- Saves changes.
- ShowRowDeleteConfirmation
- Displays the delete confirmation dialog for the specified row.
The following examples displays command buttons in the TreeList toolbar:
@inject EmployeeTaskService EmployeeTaskService
<DxTreeList @ref="TreeList"
Data="TreeListData"
KeyFieldName="Id"
ParentKeyFieldName="ParentId"
FocusedRowEnabled="true"
EditModelSaving="TreeList_EditModelSaving"
DataItemDeleting="TreeList_DataItemDeleting"
CustomizeEditModel="TreeList_CustomizeEditModel">
<Columns>
<DxTreeListDataColumn FieldName="Name" Caption="Task" />
<DxTreeListDataColumn FieldName="EmployeeName" />
<DxTreeListDataColumn FieldName="StartDate" />
<DxTreeListDataColumn FieldName="DueDate" />
</Columns>
<ToolbarTemplate>
<DxToolbar>
<DxToolbarItem Text="Add Root" Click="() => TreeList.StartEditNewRowAsync()" />
<DxToolbarItem Text="Add Child" Click="() => TreeList.StartEditNewRowAsync(TreeList.GetFocusedRowIndex())" BeginGroup="true" />
<DxToolbarItem Text="Edit" Click="() => TreeList.StartEditRowAsync(TreeList.GetFocusedRowIndex())" />
<DxToolbarItem Text="Delete" Click="() => TreeList.ShowRowDeleteConfirmation(TreeList.GetFocusedRowIndex())" />
<DxToolbarItem Text="Save" Click="() => TreeList.SaveChangesAsync()" Enabled="IsEditing" BeginGroup="true" />
<DxToolbarItem Text="Cancel" Click="() => TreeList.CancelEditAsync()" Enabled="IsEditing" />
</DxToolbar>
</ToolbarTemplate>
</DxTreeList>
@code {
ITreeList TreeList { get; set; }
List<EmployeeTask> TreeListData { get; set; }
bool IsEditing => TreeList != null ? TreeList.IsEditing() : false;
protected override void OnInitialized() {
TreeListData = EmployeeTaskService.GenerateData();
}
void TreeList_CustomizeEditModel(TreeListCustomizeEditModelEventArgs e) {
if (e.IsNew) {
var newTask = (EmployeeTask)e.EditModel;
newTask.Id = TreeListData.Max(x => x.Id) + 1;
if (e.ParentDataItem != null)
newTask.ParentId = ((EmployeeTask)e.ParentDataItem).Id;
}
}
async Task TreeList_EditModelSaving(TreeListEditModelSavingEventArgs e) {
if (e.IsNew)
TreeListData.Add((EmployeeTask)e.EditModel);
else
e.CopyChangesToDataItem();
}
async Task TreeList_DataItemDeleting(TreeListDataItemDeletingEventArgs e) {
TreeListData.Remove((EmployeeTask)e.DataItem);
}
}

Disable Column Editing
You can use one of the following column properties to prevent users from editing a specific column:
- ReadOnly
- Specifies whether column editors are in read-only mode.
- DataRowEditorVisible
- Specifies whether the edit row displays an editor for this column. If set to
false, the TreeList preserves the column in display mode and skips it during Tab navigation within the edited row.
The following code snippet prevents users from editing the ID column:
<DxTreeList Data="TreeListData"
KeyFieldName="Id"
ParentKeyFieldName="ParentId"
EditModelSaving="TreeList_EditModelSaving"
DataItemDeleting="TreeList_DataItemDeleting"
CustomizeEditModel="TreeList_CustomizeEditModel">
<Columns>
<DxTreeListCommandColumn />
<DxTreeListDataColumn FieldName="Name" Caption="Task" />
<DxTreeListDataColumn FieldName="ID" DataRowEditorVisible="false" />
<DxTreeListDataColumn FieldName="EmployeeName" />
<DxTreeListDataColumn FieldName="StartDate" />
<DxTreeListDataColumn FieldName="DueDate" />
</Columns>
</DxTreeList>
Conditionally Disable Row Editing
To prevent users from editing specific records, create a custom Edit button and disable it for the corresponding rows. The following example prevents users from editing root nodes:
<DxTreeList Data="TreeListData"
KeyFieldName="Id"
ParentKeyFieldName="ParentId"
EditModelSaving="TreeList_EditModelSaving"
DataItemDeleting="TreeList_DataItemDeleting"
CustomizeEditModel="TreeList_CustomizeEditModel">
<Columns>
<DxTreeListCommandColumn NewButtonVisible=false>
<CellDisplayTemplate>
@{
var isRootRow = (context.DataItem as EmployeeTask)?.ParentId == 0;
<DxButton IconUrl="@Icon.EditPen"
RenderStyle="ButtonRenderStyle.Link"
Attributes="@(new Dictionary<string, object> { { "aria-label", "Edit" },
{ "Title", "Edit" } })"
Click="() => context.TreeList.StartEditRowAsync(context.VisibleIndex)"
Enabled="!isRootRow" />
}
</CellDisplayTemplate>
</DxTreeListCommandColumn>
<DxTreeListDataColumn FieldName="Name" Caption="Task" />
<DxTreeListDataColumn FieldName="EmployeeName" />
<DxTreeListDataColumn FieldName="StartDate" />
<DxTreeListDataColumn FieldName="DueDate" />
</Columns>
</DxTreeList>
Use Different Edit Modes for Add and Edit Operations
The following code snippet displays a popup edit form for new rows. For existing rows, the TreeList displays an edit row:
@inject EmployeeTaskService EmployeeTaskService
<DxTreeList @ref="MyTreeList"
Data="TreeListData"
KeyFieldName="Id"
ParentKeyFieldName="ParentId"
EditMode="@editMode"
EditStart="TreeList_EditStart"
CustomizeEditModel="TreeList_CustomizeEditModel"
EditModelSaving="TreeList_EditModelSaving"
DataItemDeleting="TreeList_DataItemDeleting">
<Columns>
<DxTreeListCommandColumn />
<DxTreeListDataColumn FieldName="Name" Caption="Task" />
<DxTreeListDataColumn FieldName="EmployeeName" />
<DxTreeListDataColumn FieldName="StartDate" />
<DxTreeListDataColumn FieldName="DueDate" />
</Columns>
<EditFormTemplate Context="EditFormContext">
<DxFormLayout>
<DxFormLayoutItem Caption="Task:" ColSpanMd="6">
@EditFormContext.GetEditor("Name")
</DxFormLayoutItem>
<DxFormLayoutItem Caption="Employee Name:" ColSpanMd="6">
@EditFormContext.GetEditor("EmployeeName")
</DxFormLayoutItem>
<DxFormLayoutItem Caption="Start Date:" ColSpanMd="6">
@EditFormContext.GetEditor("StartDate")
</DxFormLayoutItem>
<DxFormLayoutItem Caption="Due Date:" ColSpanMd="6">
@EditFormContext.GetEditor("DueDate")
</DxFormLayoutItem>
</DxFormLayout>
</EditFormTemplate>
</DxTreeList>
@code {
ITreeList MyTreeList { get; set; }
private TreeListEditMode editMode = TreeListEditMode.EditRow;
List<EmployeeTask> TreeListData { get; set; }
protected override void OnInitialized() {
TreeListData = EmployeeTaskService.GenerateData();
}
void TreeList_CustomizeEditModel(TreeListCustomizeEditModelEventArgs e) {
if (e.IsNew) {
var newTask = (EmployeeTask)e.EditModel;
newTask.Id = TreeListData.Max(x => x.Id) + 1;
if (e.ParentDataItem != null)
newTask.ParentId = ((EmployeeTask)e.ParentDataItem).Id;
}
}
void TreeList_EditStart(TreeListEditStartEventArgs e) {
SetEditMode(e.IsNew ? TreeListEditMode.PopupEditForm : TreeListEditMode.EditRow);
}
void TreeList_EditModelSaving(TreeListEditModelSavingEventArgs e) {
if (e.IsNew)
TreeListData.Add((EmployeeTask)e.EditModel);
else
e.CopyChangesToDataItem();
}
void TreeList_DataItemDeleting(TreeListDataItemDeletingEventArgs e) {
TreeListData.Remove((EmployeeTask)e.DataItem);
}
void SetEditMode(TreeListEditMode mode) {
if (editMode == mode) return;
MyTreeList.BeginUpdate();
editMode = mode;
MyTreeList.EndUpdate();
}
}
Implement Cascading ComboBoxes
To implement cascading cell editors, place DxComboBox components in the CellEditTemplate properties of the related columns. Bind each ComboBox to the corresponding edit model field and use the Data property to populate cascading editors with data.
In the following example, the Country ComboBox displays the complete list of countries. When a user selects a country, the ValueChanged handler updates the Country field and clears the current City value. The City ComboBox filters its data source based on the selected country and displays only matching cities.
@inject EmployeeTaskService EmployeeTaskService
<DxTreeList Data="TreeListData"
KeyFieldName="Id"
ParentKeyFieldName="ParentId"
CustomizeEditModel="TreeList_CustomizeEditModel"
EditModelSaving="TreeList_EditModelSaving"
DataItemDeleting="TreeList_DataItemDeleting">
<Columns>
<DxTreeListCommandColumn />
<DxTreeListDataColumn FieldName="Name" Caption="Task" />
<DxTreeListDataColumn FieldName="EmployeeName" />
<DxTreeListDataColumn FieldName="StartDate" />
<DxTreeListDataColumn FieldName="DueDate" />
<DxTreeListDataColumn FieldName="Country">
<CellEditTemplate Context="cellEditContext">
@{
var editItem = (EmployeeTask)cellEditContext.EditModel;
}
<DxComboBox Data="EmployeeTaskService.GetCountries()"
NullText="Select Country..."
Value="editItem.Country"
ValueExpression="() => editItem.Country"
ValueChanged="@((string newCountry) => OnCountryChanged(editItem, newCountry))" />
</CellEditTemplate>
</DxTreeListDataColumn>
<DxTreeListDataColumn FieldName="City">
<CellEditTemplate Context="cellEditContext">
@{
var editItem = (EmployeeTask)cellEditContext.EditModel;
}
<DxComboBox Data="EmployeeTaskService.GetCities(editItem.Country)"
NullText="Select City..."
@bind-Value="editItem.City" />
</CellEditTemplate>
</DxTreeListDataColumn>
</Columns>
</DxTreeList>
@code {
private TreeListEditMode editMode = TreeListEditMode.EditCell;
List<EmployeeTask> TreeListData { get; set; }
protected override void OnInitialized() {
TreeListData = EmployeeTaskService.GenerateData();
}
void TreeList_CustomizeEditModel(TreeListCustomizeEditModelEventArgs e) {
if (e.IsNew) {
var newTask = (EmployeeTask)e.EditModel;
newTask.Id = TreeListData.Max(x => x.Id) + 1;
if (e.ParentDataItem != null)
newTask.ParentId = ((EmployeeTask)e.ParentDataItem).Id;
}
}
void TreeList_EditModelSaving(TreeListEditModelSavingEventArgs e) {
if (e.IsNew)
TreeListData.Add((EmployeeTask)e.EditModel);
else
e.CopyChangesToDataItem();
}
void TreeList_DataItemDeleting(TreeListDataItemDeletingEventArgs e) {
TreeListData.Remove((EmployeeTask)e.DataItem);
}
void OnCountryChanged(EmployeeTask item, string newCountry) {
item.Country = newCountry;
var cities = EmployeeTaskService.GetCities(newCountry);
if (!cities.Contains(item.City))
item.City = null;
}
}