Skip to main content
All docs
V26.1
  • Cell Editing in Blazor TreeList

    • 19 minutes to read

    In EditCell mode, users can click a data cell to display its in-place editor. When focus moves to another row, the control validates pending user input and saves changes.

    Blazor TreeList Cell Editing

    Run Demo: Edit Cell Run Demo: Edit Batch View Example: Implement Batch Data Editing Using Entity Framework Core

    The following shortcuts are available in EditCell mode:

    Enter or Shift+Enter

    Default behavior: Opens an in-place editor for the focused cell if an editor is not already open. If an in-place editor is open, the shortcut applies changes and closes the editor.

    Advanced behavior: Use the EnterKeyDirection property to move focus within the current row or column after the shortcut closes the editor. Set the EditNextCellOnEnter property to true to automatically display an in-place editor for the newly focused cell.

    Esc
    When a cell editor is visible, press Esc to hide the editor and discard changes made to this cell. If you press Esc when the editor is hidden, the TreeList discards all changes made to the row and cancels row editing.
    Tab or Shift+Tab
    Hides the editor in the current cell, focuses the next or previous data cell, and shows an editor in the newly focused cell.

    Enable Editing

    Follow the steps below to enable cell editing:

    1. Set the EditMode property to EditCell.
    2. 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.
    3. Handle the CustomizeEditModel event to initialize an edit model for new data rows.
    4. Handle the EditModelSaving and DataItemDeleting events to save changes and reload the TreeList data.
    5. (Optional) Declare a DxTreeListCommandColumn object in the Columns template to display the command column.
    6. (Optional) Use the EditNewRootRowPosition property to display the new item row or change the position of cell editors displayed for new root nodes.
    7. (Optional) Use the following properties to improve keyboard navigation and shortcuts: EditOnKeyPress, EditNextCellOnEnter, and EnterKeyDirection.

    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 cells:

    @inject EmployeeTaskService EmployeeTaskService
    
    <DxTreeList Data="TreeListData"
                KeyFieldName="Id"
                ParentKeyFieldName="ParentId"
                EditMode="TreeListEditMode.EditCell"
                EditModelSaving="TreeList_EditModelSaving"
                DataItemDeleting="TreeList_DataItemDeleting"
                CustomizeEditModel="TreeList_CustomizeEditModel">
        <Columns>
            <DxTreeListCommandColumn EditButtonVisible="false"
                                     CancelButtonVisible="false"
                                     SaveButtonVisible="false" />
            <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);
        }
    }
    

    Limitations

    The EditCell mode has the following specifics and limitations:

    • The mode is compatible only with DevExpress Blazor editors.
    • The TreeList component does not support this mode when you use a GridDevExtremeDataSource.
    • After a user performs a data shaping operation that hides the edited row from the view (for instance, after a user collapses the parent of the edited row), the TreeList validates row values. If validation succeeds, the TreeList saves changes. If validation fails, the TreeList discards changes and displays a notification message: “Unsaved changes were discarded”.
    • During edit operations, the TreeList applies user changes only to the edit model. The data item remains unchanged until focus leaves the edited row.

      To display the modified cell value in the CellDisplayTemplate or DataColumnCellDisplayTemplate, use the template context’s Value property instead of DisplayText. To access current values of other fields, use the corresponding fields in the edit model instead of the template context’s DataItem object. You can obtain the edit model as follows:

      <DxTreeListDataColumn FieldName="FirstName">
          <CellDisplayTemplate Context="displayContext">
              @{
                  string value;
                  if (displayContext.TreeList.IsEditingRow(displayContext.VisibleIndex)) {
                      var editModel = (Employee)displayContext.TreeList.GetEditContext().Model;
                      value = editModel.FirstName;
                  }
                  else
                      value = displayContext.DisplayText;
              }
              <span>@value</span>
          </CellDisplayTemplate>
      </DxTreeListDataColumn>
      

    This section contains comprehensive editing-related API references.

    Show API References
    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.
    EditNextCellOnEnter Property In EditCell mode, specifies whether the TreeList starts editing the next cell after a user presses Enter. Available in v26.1.4+.
    EditNewRootRowPosition Property Specifies the position of UI elements used to create new root nodes.
    EditOnKeyPress Property In EditCell mode, specifies whether cell editing starts once a user begins typing a new value.
    EnterKeyDirection Property In EditCell mode, specifies where focus moves after a user presses Enter.
    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 cell-editing functionality.

    Display New Item Row

    A new item row is an empty row that allows users to add new root nodes. Set the EditNewRootRowPosition property to FixedOnTop to display the new item row fixed to the top of the current page. Set this property to LastRow to display the new item row on the last page after the last data row.

    New Item Row in TreeList

    <DxTreeList Data="TreeListData"
                KeyFieldName="Id"
                ParentKeyFieldName="ParentId"
                EditMode="TreeListEditMode.EditCell"
                EditModelSaving="TreeList_EditModelSaving"
                DataItemDeleting="TreeList_DataItemDeleting"
                CustomizeEditModel="TreeList_CustomizeEditModel"
                EditNewRootRowPosition="TreeListEditNewRootRowPosition.FixedOnTop">
        <Columns>
            *@* ... *@
        </Columns>
    </DxTreeList>
    

    Users can press Tab or Shift + Tab to navigate between cells in a row. If focus moves beyond the last or first cell, the TreeList moves focus to the cell at the opposite end of the row and validates user input. Depending on the validation result:

    • If validation fails, the component displays error icons.
    • If validation succeeds, the component saves changes and starts editing a new root node.

    Fixed on Top Edit New Row Position

    Start Editing on Typing

    The TreeList component starts cell editing when a user clicks a cell or focuses it and presses Enter. Enable the EditOnKeyPress property to also start edit mode when a user begins typing a new value:

    <DxTreeList Data="TreeListData"
                KeyFieldName="Id"
                ParentKeyFieldName="ParentId"
                EditMode="TreeListEditMode.EditCell"
                EditModelSaving="TreeList_EditModelSaving"
                DataItemDeleting="TreeList_DataItemDeleting"
                CustomizeEditModel="TreeList_CustomizeEditModel"
                EditOnKeyPress="true">
        <Columns>
            *@* ... *@
        </Columns>
    </DxTreeList>
    

    Start Editing Next Cell on Enter

    Use the EnterKeyDirection property to focus the next cell in the current row or column after users press Enter. Set the EditNextCellOnEnter to true to automatically switch the newly focused cell to edit mode:

    <DxTreeList Data="TreeListData"
                KeyFieldName="Id"
                ParentKeyFieldName="ParentId"
                EditMode="TreeListEditMode.EditCell"
                EditModelSaving="TreeList_EditModelSaving"
                DataItemDeleting="TreeList_DataItemDeleting"
                CustomizeEditModel="TreeList_CustomizeEditModel"
                EnterKeyDirection="TreeListEnterKeyDirection.Column"
                EditNextCellOnEnter="true">
        <Columns>
            *@* ... *@
        </Columns>
    </DxTreeList>
    

    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 to display editors for data cells in this column. If set to false, the TreeList does not enter edit mode when users click a cell in the column. In edit mode, keyboard navigation with Tab or Enter skips this column.

    The following code snippet prevents users from editing the ID column:

    <DxTreeList Data="TreeListData"
                KeyFieldName="Id"
                ParentKeyFieldName="ParentId"
                EditMode="TreeListEditMode.EditCell"
                EditModelSaving="TreeList_EditModelSaving"
                DataItemDeleting="TreeList_DataItemDeleting"
                CustomizeEditModel="TreeList_CustomizeEditModel">
        <Columns>
            <DxTreeListCommandColumn EditButtonVisible="false"
                                     CancelButtonVisible="false"
                                     SaveButtonVisible="false" />
            <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 a specific record, handle the EditStart event and set the Cancel argument to true for the corresponding row. The following code sample prevents users from editing root nodes:

    @inject EmployeeTaskService EmployeeTaskService
    
    <DxTreeList Data="TreeListData"
                KeyFieldName="Id"
                ParentKeyFieldName="ParentId"
                EditMode="TreeListEditMode.EditCell"
                EditModelSaving="TreeList_EditModelSaving"
                DataItemDeleting="TreeList_DataItemDeleting"
                CustomizeEditModel="TreeList_CustomizeEditModel"
                EditStart="OnEditStart">
        <Columns>
            <DxTreeListCommandColumn EditButtonVisible="false" 
                                     CancelButtonVisible="false"
                                     SaveButtonVisible="false" />
            <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 OnEditStart(TreeListEditStartEventArgs e) {
            if ((e.DataItem as EmployeeTask)?.ParentId == 0)
                e.Cancel = true;
        }
        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);
        }
    }
    

    Conditionally Disable Cell Editing

    Use CellEditTemplate to dynamically disable editing for cells in a column based on other field values. In the template, display an editor bound to the data field and set the editor’s Enabled or ReadOnly property based on your custom condition.

    The following code sample disables Due Date column editors for root nodes:

    @inject EmployeeTaskService EmployeeTaskService
    
    <DxTreeList Data="TreeListData"
                KeyFieldName="Id"
                ParentKeyFieldName="ParentId"
                EditMode="TreeListEditMode.EditCell"
                EditModelSaving="TreeList_EditModelSaving"
                DataItemDeleting="TreeList_DataItemDeleting">
        <Columns>
            <DxTreeListCommandColumn NewButtonVisible="false"
                                     EditButtonVisible="false" 
                                     CancelButtonVisible="false"
                                     SaveButtonVisible="false" />
            <DxTreeListDataColumn FieldName="Name" Caption="Task" />
            <DxTreeListDataColumn FieldName="EmployeeName" />
            <DxTreeListDataColumn FieldName="StartDate" />
            <DxTreeListDataColumn FieldName="DueDate" >
                <CellEditTemplate Context="editContext">
                    @{
                        var task = (EmployeeTask)editContext.EditModel;
                    }
                    <DxDateEdit @bind-Date="task.DueDate" 
                                Enabled="(task.ParentId != 0)" />
                </CellEditTemplate>
            </DxTreeListDataColumn>
        </Columns>
    </DxTreeList>
    
    @code {
        List<EmployeeTask> TreeListData { get; set; }
    
        protected override void OnInitialized() {
            TreeListData = EmployeeTaskService.GenerateData();
        }
        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);
        }
    }
    

    Use Different Edit Modes for Add and Edit Operations

    The following code snippet displays a pop-up edit form for new rows. Users can edit individual cells in existing rows directly with in-place editors:

    @inject EmployeeTaskService EmployeeTaskService
    
    <DxTreeList @ref="MyTreeList"
                Data="TreeListData"
                KeyFieldName="Id"
                ParentKeyFieldName="ParentId"
                EditMode="@editMode"
                EditStart="TreeList_EditStart"
                CustomizeEditModel="TreeList_CustomizeEditModel"
                EditModelSaving="TreeList_EditModelSaving"
                EditCanceling="TreeList_EditCanceling"
                DataItemDeleting="TreeList_DataItemDeleting">
        <Columns>
            <DxTreeListCommandColumn EditButtonVisible="false"
                                 CancelButtonVisible="false"
                                 SaveButtonVisible="false" />
            <DxTreeListDataColumn FieldName="Name" Caption="Task" />
            <DxTreeListDataColumn FieldName="EmployeeName" />
            <DxTreeListDataColumn FieldName="StartDate" />
            <DxTreeListDataColumn FieldName="DueDate" />
        </Columns>
        <EditFormTemplate Context="EditFormContext">
            <DxFormLayout CssClass="edit-form-layout">
                <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.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_EditStart(TreeListEditStartEventArgs e) {
            SetEditMode(e.IsNew ? TreeListEditMode.PopupEditForm : TreeListEditMode.EditCell);
        }
    
        void TreeList_EditCanceling(TreeListEditCancelingEventArgs e) {
            SetEditMode(TreeListEditMode.EditCell);
        }
        void TreeList_EditModelSaving(TreeListEditModelSavingEventArgs e) {
            if (e.IsNew)
                TreeListData.Add((EmployeeTask)e.EditModel);
            else
                e.CopyChangesToDataItem();
            SetEditMode(TreeListEditMode.EditCell);
        }
        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 lists 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 matching cities.

    @inject EmployeeTaskService EmployeeTaskService
    
    <DxTreeList Data="TreeListData"
                KeyFieldName="Id"
                ParentKeyFieldName="ParentId"
                EditMode="TreeListEditMode.EditCell"
                CustomizeEditModel="TreeList_CustomizeEditModel"
                EditModelSaving="TreeList_EditModelSaving"
                DataItemDeleting="TreeList_DataItemDeleting">
        <Columns>
            <DxTreeListCommandColumn EditButtonVisible="false"
                                 CancelButtonVisible="false"
                                 SaveButtonVisible="false" />
            <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;
        }
    }