New to Telerik UI for WinForms? Download free 30-day trial

Keep RadTreeView states on reset

The RadTreeView is a control that allows you to visualize hierarchical structures of data in the form of a tree. However, when in bound mode, when a change in the underlying data source occurs, the tree needs to repopulate itself in order to get the latest changes. As a result, the Expanded state of the available nodes, selection and scroll bar position are not kept. This article explains how to save the tree state prior the change and restore it afterwards.

The following code snippet demonstrates how to populate the RadTreeView with hierarchical data.


public KeepTreeViewStates()
{
    InitializeComponent();

    BindingList<ChildObject> bikesChildren = new BindingList<ChildObject>();
    bikesChildren.Add(new ChildObject(567, "Bicycle"));
    bikesChildren.Add(new ChildObject(456, "Car"));
    bikesChildren.Add(new ChildObject(789, "Bike"));

    BindingList<ChildObject> clothingChildren = new BindingList<ChildObject>();
    clothingChildren.Add(new ChildObject(352, "T-shirt"));
    clothingChildren.Add(new ChildObject(981, "Dress"));

    List<ParentObject> parents = new List<ParentObject>();
    parents.Add(new ParentObject(182,"Bikes", bikesChildren));
    parents.Add(new ParentObject(346,"Accessories", null));
    parents.Add(new ParentObject(291,"Clothing", clothingChildren));

    radTreeView1.DataSource = parents;
    radTreeView1.DisplayMember = "Title\\Description";
    radTreeView1.ChildMember = "Parents\\Children";
    radTreeView1.MultiSelect = true;
}

public class ParentObject
{
    public int ID { get; set; }

    public string Title { get; set; }

    public BindingList<ChildObject> Children { get; set; }

    public ParentObject(int iD, string title, BindingList<ChildObject> children)
    {
        this.ID = iD;
        this.Title = title;
        this.Children = children;
    }
}

public class ChildObject: INotifyPropertyChanged
{ 
    public event PropertyChangedEventHandler PropertyChanged;

    private int id;
    private string description;

    public int ID
    {
        get
        {
            return this.id;
        }
        set
        {
            this.id = value;
            OnPropertyChanged("ID");
        }
    }

    public string Description
    {
        get
        {
            return this.description;
        }
        set
        {
            this.description = value;
            OnPropertyChanged("Description");
        }
    }

    public ChildObject(int iD, string description)
    {
        this.id = iD;
        this.description = description;
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

On the left figure, you can see the tree with some selected and expanded nodes and the scroll bar in the middle of the tree. On the right figure, you see how the tree is collapsed after we add a node to its data source.

Initial state After a change in the data source occurs
WinForms RadTreeView Initial state WinForms RadTreeView After a change in the data source occurs

Unfortunately, a solution of this cannot be added to the control as it depends on the case and there has to be logic added for the specific case to be handled. To keep the expanded and selected state of RadTreeView after a change in the data source occurs, we can use a Dictionary with some unique value for a key i.e. this could be the node’s Text, DataBoundItem, Value, or even an ID taken from the DataBoundItem and store the state for the node in it. The following example demonstrates how to create a simple structure - State, which will hold and describe the state of a node. The SaveExpandedStates method is used to recursively iterate all nodes in RadTreeView and populate a dictionary with the nodes information. The RestoreExpandedStates method is used to read the saved states from the dictionary, find the respective node and restore its state.


Dictionary<object, State> nodeStates = new Dictionary<object, State>();

struct State
{
    public bool Expanded { get; set; }

    public bool Selected { get; set; }

    public State(bool expanded, bool selected) : this()
    {
        this.Expanded = expanded;
        this.Selected = selected;
    }
}

private void SaveExpandedStates(RadTreeNode nodeToSave)
{
    {
        if (nodeToSave != null && nodeToSave.DataBoundItem != null)
        {
            if (! nodeStates.ContainsKey(nodeToSave.DataBoundItem))
            {
                nodeStates.Add(nodeToSave.DataBoundItem, new State(nodeToSave.Expanded, nodeToSave.Selected));
            }
            else
            {
                nodeStates[nodeToSave.DataBoundItem] = new State(nodeToSave.Expanded, nodeToSave.Selected);
            }
        }
        foreach (RadTreeNode childNode in nodeToSave.Nodes)
        {
            SaveExpandedStates(childNode);
        }
    }
}

private void RestoreExpandedStates(RadTreeNode nodeToRestore)
{
    if (nodeToRestore != null && nodeToRestore.DataBoundItem != null &&
        nodeStates.ContainsKey(nodeToRestore.DataBoundItem))
    {
        nodeToRestore.Expanded = nodeStates[nodeToRestore.DataBoundItem].Expanded;
        nodeToRestore.Selected = nodeStates[nodeToRestore.DataBoundItem].Selected;
    }

    foreach (RadTreeNode childNode in nodeToRestore.Nodes)
    {
        RestoreExpandedStates(childNode);
    }
}

Once we have these methods implemented, we can use the dictionary to save the RadTreeView state prior the change occurs and restore it afterwards. In the example below, we are also saving and restoring the scroll bar position.


private void radButtonAdd_Click(object sender, EventArgs e)
{
    int scrollBarValue = radTreeView1.VScrollBar.Value;           
    foreach (RadTreeNode nodeToSave in radTreeView1.Nodes)
    {
        SaveExpandedStates(nodeToSave);
    }

    ParentObject parent = radTreeView1.Nodes[0].DataBoundItem as ParentObject;
    parent.Children.Add(new ChildObject(673,"New child"));    

    radTreeView1.TreeViewElement.Update(RadTreeViewElement.UpdateActions.ItemAdded);

    foreach (RadTreeNode nodeToRestore in radTreeView1.Nodes)
    {
        RestoreExpandedStates(nodeToRestore);
    }
    radTreeView1.VScrollBar.Value = scrollBarValue;
}

private void radButtonDelete_Click(object sender, EventArgs e)
{
    int scrollBarValue = radTreeView1.VScrollBar.Value;           
    foreach (RadTreeNode nodeToSave in radTreeView1.Nodes)
    {
        SaveExpandedStates(nodeToSave);
    }

    ParentObject parent = radTreeView1.Nodes[0].DataBoundItem as ParentObject;
    parent.Children.Clear();

    radTreeView1.TreeViewElement.Update(RadTreeViewElement.UpdateActions.ItemRemoved);

    foreach (RadTreeNode nodeToRestore in radTreeView1.Nodes)
    {
        RestoreExpandedStates(nodeToRestore);
    }
    radTreeView1.VScrollBar.Value = scrollBarValue;
}

private void radButtonUpdate_Click(object sender, EventArgs e)
{
    int scrollBarValue = radTreeView1.VScrollBar.Value;           
    foreach (RadTreeNode nodeToSave in radTreeView1.Nodes)
    {
        SaveExpandedStates(nodeToSave);
    }

    ParentObject parent = radTreeView1.Nodes[2].DataBoundItem as ParentObject;
    parent.Children[1].Description = "New description";

    foreach (RadTreeNode nodeToRestore in radTreeView1.Nodes)
    {
        RestoreExpandedStates(nodeToRestore);
    }
    radTreeView1.VScrollBar.Value = scrollBarValue;
}

Now, using these methods the tree states will be restored accordingly.

Initial state After a change in the data source occurs
WinForms RadTreeView treeview-how-to-keep-radtreeview-states-on-reset 003 WinForms RadTreeView treeview-how-to-keep-radtreeview-states-on-reset 004
In this article