New to Telerik UI for BlazorStart a free 30-day trial

TreeList State

The TreeList lets you read, save, load, and change its state through code. The state includes the TreeList features that are controlled by the user, such as the current sorting, page number, applied grouping, column widths, and many others.

This article describes:

Information in the TreeList State

The TreeList state is a generic class TreeListState<TItem>. The type depends on the type of the TreeList model. The TreeListState<TItem> object exposes the following properties:

PropertyTypeDescription
ColumnStatesICollection<TreeListColumnState>Information about each column's reorder index, width, visibility, locked state, Id parameter value and Field. The column order in the collection matches the column order in the TreeList declaration. On the other hand, the Index property matches the current column's position in the UI. Id and Field are always null after deserialization, because these properties have no public setters.
EditFieldstringThe currently edited data item property in Incell edit mode.
EditItemTItem*The currently edited data item in any edit mode.
ExpandedItemsICollection<TItem>The expanded data items.
FilterDescriptorsICollection<IFilterDescriptor>A collection of CompositeFilterDescriptor, except the ones that relate to the TreeListSearchBox.
InsertedItemTItem*The data item that is being added in Inline or Popup edit mode. Not applicable for Incell editing.
OriginalEditItemTItem*The original copy of the data item that is currently in edit mode. This TreeListState property holds the unmodified data item values.
Pageint?The current page index. Some user actions reset the page index to 1, such as filtering or changing the page size.
ParentItemTItem?*The parent item of the current InsertedItem in Inline or Popup edit mode. The value is null is the new item is being added at root level.
SearchFilterIFilterDescriptorThe CompositeFilterDescriptor that holds the filter descriptors for the TreeListSearchBox.
SelectedItemsICollection<TItem>The currently selected data item(s).
Skipint?The number of scrolled data items when using virtual row scrolling. In other words, this is the number of rows above the currently visible ones.
SortDescriptorsICollection<SortDescriptor>The currently applied sorts.
TableWidthstringThe sum of all visible column widths. This property changes together with ColumnStates. The OnStateChanged event does not fire separately for it.

* TItem is the TreeList model type.

Events

The TreeList features two events, which are related to its state.

OnStateInit

The OnStateInit event fires when the TreeList is initializing. Use this event to:

  • Define initial state, for example default initial sorting;
  • Load and apply state that was previously saved in a database or in localStorage.

The generic event argument is of type TreeListStateEventArgs<TItem> and has a TreeListState property. See Information in the TreeList State for details.

If you change the column order or number of columns in the TreeList declaration, this can break state restore. In such cases, either ignore the stored column state, or implement custom logic to restore only the columns that still exist in the TreeList.

To set the initial visibility of columns, better use the Visible parameter, rather than conditional markup for the whole column. The Visible parameter values will be present in the TreeList state and the columns collection count will remain the same. This makes it easier to reconcile changes.

The example below shows how to apply initial sorting, filtering and grouping.

Using TreeList OnStateInit

@using System.ComponentModel.DataAnnotations
@using Telerik.DataSource
@using Telerik.DataSource.Extensions

<TelerikTreeList Data="@TreeListData"
                 IdField="@nameof(Employee.Id)"
                 ParentIdField="@nameof(Employee.ParentId)"
                 FilterMode="@TreeListFilterMode.FilterMenu"
                 OnStateInit="@( (TreeListStateEventArgs<Employee> args) => OnTreeListStateInit(args) )"
                 Pageable="true"
                 Sortable="true"
                 Height="400px">
    <TreeListColumns>
        <TreeListColumn Field="@nameof(Employee.Name)" Expandable="true" />
        <TreeListColumn Field="@nameof(Employee.Salary)" DisplayFormat="{0:C2}" Width="130px" />
        <TreeListColumn Field="@nameof(Employee.HireDate)" DisplayFormat="{0:d}" Width="140px" />
        <TreeListColumn Field="@nameof(Employee.IsDriver)" Width="120px" />
    </TreeListColumns>
</TelerikTreeList>

@code {
    private IEnumerable<Employee>? TreeListData { get; set; }

    private EmployeeService TreeListEmployeeService { get; set; } = new();

    private void OnTreeListStateInit(TreeListStateEventArgs<Employee> args)
    {
        // Sort sibling items by Salary
        args.TreeListState.SortDescriptors.Add(new SortDescriptor()
        {
            Member = nameof(Employee.Salary),
            SortDirection = ListSortDirection.Descending
        });

        // Filter by IsDriver
        var driverColumnFilter = new CompositeFilterDescriptor()
        {
            FilterDescriptors = new FilterDescriptorCollection() {
                 new FilterDescriptor()
                 {
                    Member = nameof(Employee.IsDriver),
                    MemberType = typeof(bool),
                    Operator = FilterOperator.IsEqualTo,
                    Value = true
                 }
             }
        };

        args.TreeListState.FilterDescriptors.Add(driverColumnFilter);
    }

    protected override async Task OnInitializedAsync()
    {
        TreeListData = await TreeListEmployeeService.Read();
    }

    public class Employee
    {
        public int Id { get; set; }
        public int? ParentId { get; set; }
        public bool HasChildren { get; set; }
        [Required]
        public string Name { get; set; } = string.Empty;
        public string Notes { get; set; } = string.Empty;
        [Required]
        public decimal? Salary { get; set; }
        [Required]
        public DateTime? HireDate { get; set; }
        public bool IsDriver { get; set; }

        public override bool Equals(object? obj)
        {
            return obj is Employee && ((Employee)obj).Id == Id;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }

    #region Data Service

    public class EmployeeService
    {
        private List<Employee> Items { get; set; } = new();

        private readonly int TreeLevelCount;
        private readonly int RootItemCount;
        private readonly int ChildItemCount;

        private int LastId { get; set; }
        private Random Rnd { get; set; } = Random.Shared;

        public async Task<int> Create(Employee createdEmployee, Employee? parentEmployee)
        {
            await SimulateAsyncOperation();

            createdEmployee.Id = ++LastId;
            createdEmployee.ParentId = parentEmployee?.Id;

            Items.Insert(0, createdEmployee);

            if (parentEmployee != null)
            {
                parentEmployee.HasChildren = true;
            }

            return LastId;
        }

        public async Task<bool> Delete(Employee deletedEmployee)
        {
            await SimulateAsyncOperation();

            if (Items.Contains(deletedEmployee))
            {
                DeleteChildren(deletedEmployee.Id);
                Items.Remove(deletedEmployee);

                if (deletedEmployee.ParentId.HasValue && !Items.Any(x => x.ParentId == deletedEmployee.ParentId.Value))
                {
                    Items.First(x => x.Id == deletedEmployee.ParentId.Value).HasChildren = false;
                }

                return true;
            }

            return false;
        }

        public async Task<List<Employee>> Read()
        {
            await SimulateAsyncOperation();

            return Items;
        }

        public async Task<DataSourceResult> Read(DataSourceRequest request)
        {
            return await Items.ToDataSourceResultAsync(request);
        }

        public async Task<bool> Update(Employee updatedEmployee)
        {
            await SimulateAsyncOperation();

            int originalItemIndex = Items.FindIndex(x => x.Id == updatedEmployee.Id);

            if (originalItemIndex != -1)
            {
                Items[originalItemIndex] = updatedEmployee;
                return true;
            }

            return false;
        }

        private async Task SimulateAsyncOperation()
        {
            await Task.Delay(100);
        }

        private void DeleteChildren(int parentId)
        {
            List<Employee> children = Items.Where(x => x.ParentId == parentId).ToList();

            foreach (Employee child in children)
            {
                DeleteChildren(child.Id);
            }

            Items.RemoveAll(x => x.ParentId == parentId);
        }

        private void PopulateChildren(List<Employee> items, int? parentId, int level)
        {
            int itemCount = level == 1 ? RootItemCount : ChildItemCount;

            for (int i = 1; i <= itemCount; i++)
            {
                int itemId = ++LastId;

                items.Add(new Employee()
                {
                    Id = itemId,
                    ParentId = parentId,
                    HasChildren = level < TreeLevelCount,
                    Name = $"Employee Name {itemId}", // {level}-{i}
                    Notes = $"Multi-line\nnotes {itemId}",
                    Salary = Rnd.Next(1_000, 10_000) * 1.23m,
                    HireDate = DateTime.Today.AddDays(-Rnd.Next(365, 3650)),
                    IsDriver = itemId % 2 == 0
                });

                if (level < TreeLevelCount)
                {
                    PopulateChildren(items, itemId, level + 1);
                }
            }
        }

        public EmployeeService(int treeLevelCount = 3, int rootItemCount = 3, int childItemCount = 2)
        {
            TreeLevelCount = treeLevelCount;
            RootItemCount = rootItemCount;
            ChildItemCount = childItemCount;

            List<Employee> items = new();
            PopulateChildren(items, null, 1);

            Items = items;
        }
    }

    #endregion Data Service
}

OnStateChanged

OnStateChanged fires when the user performs an action that changes the value of a property in the TreeList state. The event argument is of type TreeListStateEventArgs<TItem> and exposes these properties:

PropertyTypeDescription
PropertyNamestringInformation about what changed in the TreeList state. The possible values match the property names of the TreeListState object. The possible values for the PropertyName are SortDescriptors, FilterDescriptors, SearchFilter, Page, Skip, ColumnStates, ExpandedItems, InsertedItem, OriginalEditItem, EditItem.
TreeListStateTreeListState<TItem>The current (up-to-date) TreeList state object.

Here is some additional information about certain PropertyName values:

  • EditItem is used when the user starts editing an existing item.
  • InsertedItem signifies the user adding a new item in inline or popup edit mode. It's not applicable for Incell editing.
  • OriginalEditItem is used when the user exits edit or insert mode via save or cancel.
  • ColumnStates is used for several column actions such as hiding, showing, locking, reordering and resizing.

Some user actions will trigger two OnStateChanged events with a different PropertyName each time. These include filtering and searching. For example, filtering resets the current page to 1. First, the event will fire with PropertyName equal to "FilterDescriptors", and then PropertyName will be "Page". However, the TreeListState property of the event argument will provide correct information about the overall TreeList state in both event handler executions.

We recommend using an async Task handler for the OnStateChanged event, in order to reduce re-rendering and avoid blocking UI updates if the handler will wait for a service to save the TreeList state somewhere.

To observe the changes in the TreeList state more easily, copy and run the following example in a local app and at full screen.

Find out how to get the applied filtering and sorting criteria.

Using TreeList OnStateChanged

@using System.ComponentModel.DataAnnotations
@using System.Text.Json
@using Telerik.DataSource
@using Telerik.DataSource.Extensions

<div id="demo-container">
    <TelerikTreeList Data="@TreeListData"
                     IdField="@nameof(Employee.Id)"
                     ParentIdField="@nameof(Employee.ParentId)"
                     ConfirmDelete="true"
                     EditMode="@TreeListEditMode.Inline"
                     FilterMode="@TreeListFilterMode.FilterMenu"
                     OnCreate="@OnTreeListCreate"
                     OnUpdate="@OnTreeListUpdate"
                     OnStateChanged="@( (TreeListStateEventArgs<Employee> args) => OnTreeListStateChanged(args) )"
                     Pageable="true"
                     @bind-PageSize="@TreeListPageSize"
                     Reorderable="true"
                     Resizable="true"
                     @bind-SelectedItems="@TreeListSelectedItems"
                     SelectionMode="@TreeListSelectionMode.Multiple"
                     ShowColumnMenu="true"
                     Sortable="true"
                     Height="400px">
        <TreeListSettings>
            <TreeListPagerSettings PageSizes="@( new List<int?>() { null, 5, 10 } )" />
        </TreeListSettings>
        <TreeListToolBarTemplate>
            <TreeListCommandButton Command="Add">Add Item</TreeListCommandButton>
            <TreeListSearchBox />
        </TreeListToolBarTemplate>
        <TreeListColumns>
            <TreeListCheckboxColumn SelectAll="true" />
            <TreeListColumn Field="@nameof(Employee.Name)" Expandable="true" />
            <TreeListColumn Field="@nameof(Employee.Salary)" DisplayFormat="{0:C2}" Width="130px" />
            <TreeListColumn Field="@nameof(Employee.HireDate)" DisplayFormat="{0:d}" Width="140px" />
            <TreeListColumn Field="@nameof(Employee.IsDriver)" Width="120px" />
            <TreeListCommandColumn Width="160px">
                <TreeListCommandButton Command="Add">Add</TreeListCommandButton>
                <TreeListCommandButton Command="Edit">Edit</TreeListCommandButton>
                <TreeListCommandButton Command="Save" ShowInEdit="true">Save</TreeListCommandButton>
                <TreeListCommandButton Command="Cancel" ShowInEdit="true">Cancel</TreeListCommandButton>
            </TreeListCommandColumn>
        </TreeListColumns>
    </TelerikTreeList>

    <div id="console">
        <code class="@TreeListStateChangedPropertyClass">OnStateChanged</code> count:
        @OnStateChangedCount
        <TelerikButton OnClick="@( () => OnStateChangedCount = 0 )">Reset</TelerikButton>
        <br /><br />
        Last <code>OnStateChanged</code> event:
        <br />
        <strong class="@TreeListStateChangedPropertyClass">PropertyName</strong>:
        <code>&quot;@TreeListStateChangedProperty&quot;</code>
        <br />
        <strong>TreeListState</strong>:
        <pre>
        @( new MarkupString(TreeListStateString) )
        </pre>
    </div>
</div>

<style>
    .first-of-two {
        color: #f00;
    }

    .latest-changed-property {
        color: #00f;
    }

    @@media (min-width: 800px) {
        #demo-container {
            display: flex;
            align-items: flex-start;
            gap: 1em;
        }

            #demo-container > .k-treelist {
                flex: 2 2 800px;
            }

        #console {
            height: 90vh;
            overflow: auto;
            flex: 1 0 300px;
            border: 1px solid rgba(128, 128, 128, .3);
            padding: 1em;
        }
    }
</style>

@code {
    private IEnumerable<Employee>? TreeListData { get; set; }

    private int TreeListPageSize { get; set; } = 5;

    private IEnumerable<Employee> TreeListSelectedItems { get; set; } = new List<Employee>();

    private EmployeeService TreeListEmployeeService { get; set; } = new();

    private int OnStateChangedCount { get; set; }

    private string TreeListStateChangedProperty { get; set; } = string.Empty;
    private string TreeListStateChangedPropertyClass { get; set; } = string.Empty;

    private string TreeListStateString { get; set; } = string.Empty;

    private bool _doubleStateChanged { get; set; }

    private List<string> _operationsWithMultipleStateChanged = new List<string>() {
        "FilterDescriptors",
        "SearchFilter"
    };

    private async Task OnTreeListStateChanged(TreeListStateEventArgs<Employee> args)
    {
        if (_doubleStateChanged)
        {
            _doubleStateChanged = false;
            await Task.Delay(1500);
            TreeListStateChangedPropertyClass = string.Empty;
        }

        ++OnStateChangedCount;

        TreeListStateChangedProperty = args.PropertyName;

        // serialize the TreeListState and highlight the changed property
        TreeListStateString = JsonSerializer.Serialize(args.TreeListState, new JsonSerializerOptions() { WriteIndented = true })
        .Replace($"\"{TreeListStateChangedProperty}\"", $"\"<strong class='latest-changed-property'>{TreeListStateChangedProperty}</strong>\"");

        // highlight first TreeListStateChangedProperty during filtering, grouping and search
        if (_operationsWithMultipleStateChanged.Contains(TreeListStateChangedProperty))
        {
            _doubleStateChanged = true;
            TreeListStateChangedPropertyClass = "first-of-two";
        }
    }

    private async Task OnTreeListCreate(TreeListCommandEventArgs args)
    {
        var createdItem = (Employee)args.Item;
        var parentItem = (Employee?)args.ParentItem;

        await TreeListEmployeeService.Create(createdItem, parentItem);

        TreeListData = await TreeListEmployeeService.Read();
    }

    private async Task OnTreeListUpdate(TreeListCommandEventArgs args)
    {
        var updatedItem = (Employee)args.Item;

        await TreeListEmployeeService.Update(updatedItem);

        TreeListData = await TreeListEmployeeService.Read();
    }

    protected override async Task OnInitializedAsync()
    {
        TreeListData = await TreeListEmployeeService.Read();
    }

    public class Employee
    {
        public int Id { get; set; }
        public int? ParentId { get; set; }
        public bool HasChildren { get; set; }
        [Required]
        public string Name { get; set; } = string.Empty;
        public string Notes { get; set; } = string.Empty;
        [Required]
        public decimal? Salary { get; set; }
        [Required]
        public DateTime? HireDate { get; set; }
        public bool IsDriver { get; set; }

        public override bool Equals(object? obj)
        {
            return obj is Employee && ((Employee)obj).Id == Id;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }

    #region Data Service

    public class EmployeeService
    {
        private List<Employee> Items { get; set; } = new();

        private readonly int TreeLevelCount;
        private readonly int RootItemCount;
        private readonly int ChildItemCount;

        private int LastId { get; set; }
        private Random Rnd { get; set; } = Random.Shared;

        public async Task<int> Create(Employee createdEmployee, Employee? parentEmployee)
        {
            await SimulateAsyncOperation();

            createdEmployee.Id = ++LastId;
            createdEmployee.ParentId = parentEmployee?.Id;

            Items.Insert(0, createdEmployee);

            if (parentEmployee != null)
            {
                parentEmployee.HasChildren = true;
            }

            return LastId;
        }

        public async Task<bool> Delete(Employee deletedEmployee)
        {
            await SimulateAsyncOperation();

            if (Items.Contains(deletedEmployee))
            {
                DeleteChildren(deletedEmployee.Id);
                Items.Remove(deletedEmployee);

                if (deletedEmployee.ParentId.HasValue && !Items.Any(x => x.ParentId == deletedEmployee.ParentId.Value))
                {
                    Items.First(x => x.Id == deletedEmployee.ParentId.Value).HasChildren = false;
                }

                return true;
            }

            return false;
        }

        public async Task<List<Employee>> Read()
        {
            await SimulateAsyncOperation();

            return Items;
        }

        public async Task<DataSourceResult> Read(DataSourceRequest request)
        {
            return await Items.ToDataSourceResultAsync(request);
        }

        public async Task<bool> Update(Employee updatedEmployee)
        {
            await SimulateAsyncOperation();

            int originalItemIndex = Items.FindIndex(x => x.Id == updatedEmployee.Id);

            if (originalItemIndex != -1)
            {
                Items[originalItemIndex] = updatedEmployee;
                return true;
            }

            return false;
        }

        private async Task SimulateAsyncOperation()
        {
            await Task.Delay(100);
        }

        private void DeleteChildren(int parentId)
        {
            List<Employee> children = Items.Where(x => x.ParentId == parentId).ToList();

            foreach (Employee child in children)
            {
                DeleteChildren(child.Id);
            }

            Items.RemoveAll(x => x.ParentId == parentId);
        }

        private void PopulateChildren(List<Employee> items, int? parentId, int level)
        {
            int itemCount = level == 1 ? RootItemCount : ChildItemCount;

            for (int i = 1; i <= itemCount; i++)
            {
                int itemId = ++LastId;

                items.Add(new Employee()
                {
                    Id = itemId,
                    ParentId = parentId,
                    HasChildren = level < TreeLevelCount,
                    Name = $"Employee Name {itemId}", // {level}-{i}
                    Notes = $"Multi-line\nnotes {itemId}",
                    Salary = Rnd.Next(1_000, 10_000) * 1.23m,
                    HireDate = DateTime.Today.AddDays(-Rnd.Next(365, 3650)),
                    IsDriver = itemId % 2 == 0
                });

                if (level < TreeLevelCount)
                {
                    PopulateChildren(items, itemId, level + 1);
                }
            }
        }

        public EmployeeService(int treeLevelCount = 3, int rootItemCount = 3, int childItemCount = 2)
        {
            TreeLevelCount = treeLevelCount;
            RootItemCount = rootItemCount;
            ChildItemCount = childItemCount;

            List<Employee> items = new();
            PopulateChildren(items, null, 1);

            Items = items;
        }
    }

    #endregion Data Service
}

Methods

The GetState and SetStateAsync methods of the TreeList instance let you get and set the current TreeList state on demand at any time after OnStateInit.

If you want to make changes to the current TreeList state:

  1. First, get the current state with the GetState method.
  2. Apply the desired modifications to the obtained TreeListState object.
  3. Set the modified state object via the SetStateAsync method.

Do not use GetState() in the OnStateInit or OnStateChanged events. Do not use SetStateAsync() in OnStateInit. Instead, get or set the TreeListState property of the event argument.

Avoid calling SetStateAsync in the TreeList CRUD methods (such as OnUpdate, OnEdit, OnCreate, OnCancel). Doing so may lead to unexpected results because the TreeList has more logic to execute after these events.

To reset the TreeList state to its initial markup configuration, call SetStateAsync(null).

To reset the TreeList state to a completely new configuration, create a new TreeListState<T>() and apply the settings there. Then pass the state object to SetStateAsync().

SetStateAsync Examples

The tabs below show how to set the TreeList state and control filtering, sorting and other TreeList features.

If you want to set an initial state to the TreeList, use a similar snippet, but in the OnStateInit event

RAZOR
@* This snippet shows how to set sorting state to the TreeList from your code *@

@using Telerik.DataSource;

<TelerikButton OnClick="@SetTreeListSort">Set sorted state</TelerikButton>

<TelerikTreeList Data="@Data"
                 ItemsField="@(nameof(Employee.DirectReports))"
                 Reorderable="true"
                 Resizable="true"
                 Sortable="true"
                 Pageable="true" 
                 Width="850px" 
                 @ref="TreeListRef">
    <TreeListColumns>
        <TreeListColumn Field="Name" Expandable="true" Width="320px" />
        <TreeListColumn Field="Id" Editable="false" Width="120px" />
        <TreeListColumn Field="EmailAddress" Width="220px" />
        <TreeListColumn Field="HireDate" Width="220px" />
    </TreeListColumns>
</TelerikTreeList>

@code {
    public TelerikTreeList<Employee> TreeListRef { get; set; } = new TelerikTreeList<Employee>();

    async Task SetTreeListSort()
    {
        var sortedState = new TreeListState<Employee>()
        {
            SortDescriptors = new List<SortDescriptor>()
            {
                new SortDescriptor(){ Member = nameof(Employee.Id), SortDirection = ListSortDirection.Descending }
            }
        };

        await TreeListRef.SetStateAsync(sortedState);
    }

    public List<Employee> Data { get; set; }

    // sample model

    public class Employee
    {
        // hierarchical data collections
        public List<Employee> DirectReports { get; set; }

        // data fields for display
        public int Id { get; set; }
        public string Name { get; set; }
        public string EmailAddress { get; set; }
        public DateTime HireDate { get; set; }
    }

    // data generation

    // used in this example for data generation and retrieval for CUD operations on the current view-model data
    public int LastId { get; set; } = 1;

    protected override async Task OnInitializedAsync()
    {
        Data = await GetTreeListData();
    }

    async Task<List<Employee>> GetTreeListData()
    {
        List<Employee> data = new List<Employee>();

        for (int i = 1; i < 15; i++)
        {
            Employee root = new Employee
            {
                Id = LastId,
                Name = $"root: {i}",
                EmailAddress = $"{i}@example.com",
                HireDate = DateTime.Now.AddYears(-i),
                DirectReports = new List<Employee>(), // prepare a collection for the child items, will be populated later in the code
            };
            data.Add(root);
            LastId++;

            for (int j = 1; j < 4; j++)
            {
                int currId = LastId;
                Employee firstLevelChild = new Employee
                {
                    Id = currId,
                    Name = $"first level child {j} of {i}",
                    EmailAddress = $"{currId}@example.com",
                    HireDate = DateTime.Now.AddDays(-currId),
                    DirectReports = new List<Employee>(), // collection for child nodes
                };
                root.DirectReports.Add(firstLevelChild); // populate the parent's collection
                LastId++;

                for (int k = 1; k < 3; k++)
                {
                    int nestedId = LastId;
                    // populate the parent's collection
                    firstLevelChild.DirectReports.Add(new Employee
                    {
                        Id = LastId,
                        Name = $"second level child {k} of {j} and {i}",
                        EmailAddress = $"{nestedId}@example.com",
                        HireDate = DateTime.Now.AddMinutes(-nestedId)
                    }); ;
                    LastId++;
                }
            }
        }

        return await Task.FromResult(data);
    }
}

If you want to alter the filters for a specific column, do not use more than one FilterDescriptor in FilterRow mode, and more than two FilterDescriptors in FilterMenu mode. Otherwise additional descriptors will not show up in the UI. This means that you may need to replace or modify an existing descriptor, rather than add a new one.

Inactive filter descriptors in FilterMenu mode are distinguished by their null Value.

Equals Comparison

State properties that pertain to data items (for example, edited item or selected items) are typed according to the TreeList model. If you restore such data, make sure to implement appropriate comparison checks - by default the .Equals() check for a class (object) is a reference check and the reference from the restored state is very unlikely to match the current reference in the TreeList data. Thus, you may want to override the .Equals() method of the TreeList model class, so that it compares by ID, or otherwise re-populate the models in the state object with the new model references from the TreeList data.

Examples

You can find multiple examples for using the TreeList state in the following Knowledge Base articles:

See Also