Declined
Last Updated: 13 Jun 2018 14:25 by ADMIN
ADMIN
Hristo
Created on: 11 May 2018 13:47
Category: TreeView
Type: Bug Report
0
FIX. RadTreeView - exception after adding a new node from the default context menu and the control is data bound to self-referencing data
How to reproduce:
public partial class RadForm1 : Telerik.WinControls.UI.RadForm
{
    private BindingList<TreeViewDataObject> data;
    public static int Id = 0;

    public RadForm1()
    {
        InitializeComponent();

        var theme = new FluentTheme();
        ThemeResolutionService.ApplicationThemeName = theme.ThemeName;

        this.data = new BindingList<TreeViewDataObject>();


        for (int i = 1; i <= 5; i += 1)
        {
            Id++;
            TreeViewDataObject root = new TreeViewDataObject()
            {
                Id = Id,
                ParentId = -1,
                Name = "Root: " + i
            };

            this.data.Add(root);

            for (int j = 1; j <= 3; j++)
            {
                Id++;
                TreeViewDataObject child = new TreeViewDataObject()
                {
                    Id = Id,
                    ParentId = root.Id,
                    Name = "Child: " + Id
                };

                this.data.Add(child);

                for (int K = 1; K <= 5; K++)
                {
                    Id++;

                    TreeViewDataObject c = new TreeViewDataObject()
                    {
                        Id = Id,
                        ParentId = child.Id,
                        Name = "Child: " + Id
                    };

                    this.data.Add(c);
                }
            }
        }

        this.radTreeView1.DataSource = this.data;
        this.radTreeView1.DisplayMember = "Name";
        this.radTreeView1.ParentMember = "ParentId";
        this.radTreeView1.ChildMember = "Id";
        this.radTreeView1.RelationBindings.Add(new RelationBinding(this.data, "Name", "ParentId", "Id"));

        this.radTreeView1.ExpandAll();

        this.radTreeView1.TreeViewElement.AllowEdit = true;
        this.radTreeView1.TreeViewElement.AllowAdd = true;
        this.radTreeView1.TreeViewElement.AllowRemove = true;
    }
}

  public class TreeViewDataObject
    {
        public int Id { get; set; }

        public int ParentId { get; set; }

        public string Name { get; set; }
    }

Workaround: Handle the ContextMenuOpening event and add logic for creating a new record in the data source object
public partial class RadForm1 : Telerik.WinControls.UI.RadForm
{
    private BindingList<TreeViewDataObject> data;
    private int id = 0;

    public RadForm1()
    {
        InitializeComponent();

        var theme = new FluentTheme();
        ThemeResolutionService.ApplicationThemeName = theme.ThemeName;

        this.data = new BindingList<TreeViewDataObject>();


        for (int i = 1; i <= 5; i += 1)
        {
            id++;
            TreeViewDataObject root = new TreeViewDataObject()
            {
                Id = id,
                ParentId = -1,
                Name = "Root: " + i
            };

            this.data.Add(root);

            for (int j = 1; j <= 3; j++)
            {
                id++;
                TreeViewDataObject child = new TreeViewDataObject()
                {
                    Id = id,
                    ParentId = root.Id,
                    Name = "Child: " + id
                };

                this.data.Add(child);

                for (int K = 1; K <= 5; K++)
                {
                    id++;

                    TreeViewDataObject c = new TreeViewDataObject()
                    {
                        Id = id,
                        ParentId = child.Id,
                        Name = "Child: " + id
                    };

                    this.data.Add(c);
                }
            }
        }

        this.radTreeView1.DataSource = this.data;
        this.radTreeView1.DisplayMember = "Name";
        this.radTreeView1.ParentMember = "ParentId";
        this.radTreeView1.ChildMember = "id";
        this.radTreeView1.RelationBindings.Add(new RelationBinding(this.data, "Name", "ParentId", "id"));

        this.radTreeView1.ExpandAll();
        
        this.radTreeView1.ContextMenuOpening += RadTreeView1_ContextMenuOpening;

        this.radTreeView1.TreeViewElement.AllowEdit = true;
        this.radTreeView1.TreeViewElement.AllowAdd = true;
        this.radTreeView1.TreeViewElement.AllowRemove = true;
    }

    private void RadTreeView1_ContextMenuOpening(object sender, TreeViewContextMenuOpeningEventArgs e)
    {
        TreeViewDefaultContextMenu treeMenu = e.Menu as TreeViewDefaultContextMenu;
        treeMenu.Items.Remove(treeMenu.AddMenuItem);

        RadMenuItem item = new RadMenuItem("&New");

        item.Click -= Item_Click;
        item.Click += Item_Click;
        treeMenu.Items.Insert(2, item);
    }

    private void Item_Click(object sender, EventArgs e)
    {
        RadTreeNode parent = this.radTreeView1.SelectedNode;
        if (parent == null || parent.DataBoundItem == null)
        {
            return;
        }

        this.id++;
        BindingList<TreeViewDataObject> data = this.radTreeView1.DataSource as BindingList<TreeViewDataObject>;
        TreeViewDataObject newObject = new TreeViewDataObject()
        {
            Id = this.id,
            ParentId = ((TreeViewDataObject)parent.DataBoundItem).Id,
            Name = "New Name"
        };

        parent.Expanded = true;
        data.Add(newObject);
    }
}

public class TreeViewDataObject
{
    public int Id { get; set; }

    public int ParentId { get; set; }

    public string Name { get; set; }
}
(Total attached files size should be smaller than 20mb. Allowed extensions: .zip, .rar, .jpg, .png, .gif)
1 comment
ADMIN
Dimitar
Posted on: 13 Jun 2018 14:25
The binding provider used by RadTreeView is properly handling any child nodes added via the default context menu to the tree. The issue with the sample code reproduces an exception. This exception is thrown on purpose and it is caused by the sample code in which the Id property of the objects is not unique. The data-bound item of the tree nodes added by the context menu is created with the CurrencyManager class and its AddNew method. This method will call the empty constructor of the object and its Id will remain not set which may result in nodes having the same Ids.

This scenario can be handled by subscribing the tree to its NodeDataBound event. The attached project - RadTreeViewSelfReferenceContextMenu handles three different scenarios:
DataTableForm - binding the control to a self-referencing DataTable. It is important to have the Id column`s AutoIncrement property set to true.
AutoIncrementDataObjectForm - the control is bound to a collection of self-referencing objects. It uses the approach suggested in StackOverflow(https://stackoverflow.com/questions/9262221/c-sharp-class-auto-increment-id) to automatically increment the Id property of the object
NodeDataBoundEventForm - in this form the control is bound to a simple object with three properties and the developer is responsible for setting the unique Ids of the object. The tree exposes the NodeDataBound event which can be used to set the Id property on the data-bound object. Adding a new node from the context menu will raise the event.

The code snippet below is a simple example of how the Id can be incremented by one every time a node is added via the context menu.

public partial class NodeDataBoundEventForm : Telerik.WinControls.UI.RadForm
{
private BindingList<TreeViewData> data;
private int id = 0;

public NodeDataBoundEventForm()
{
InitializeComponent();

this.data = new BindingList<TreeViewData>();
for (int i = 1; i <= 5; i += 1)
{
id++;
TreeViewData root = new TreeViewData()
{
Id = id,
ParentId = -1,
Name = "Root: " + i
};

this.data.Add(root);

for (int j = 1; j <= 3; j++)
{
id++;
TreeViewData child = new TreeViewData()
{
Id = id,
ParentId = root.Id,
Name = "Child: " + id
};

this.data.Add(child);

for (int K = 1; K <= 5; K++)
{
id++;
TreeViewData c = new TreeViewData()
{
Id = id,
ParentId = child.Id,
Name = "Child: " + id
};

this.data.Add(c);
}
}
}

this.radTreeView1.DataSource = this.data;
this.radTreeView1.DisplayMember = "Name";
this.radTreeView1.ParentMember = "ParentId";
this.radTreeView1.ChildMember = "Id";


this.radTreeView1.ExpandAll();

this.radTreeView1.TreeViewElement.AllowEdit = true;
this.radTreeView1.TreeViewElement.AllowAdd = true;
this.radTreeView1.TreeViewElement.AllowRemove = true;
this.radTreeView1.AllowDefaultContextMenu = true;

this.radTreeView1.NodeDataBound += radTreeView1_NodeDataBound;
}

void radTreeView1_NodeDataBound(object sender, Telerik.WinControls.UI.RadTreeViewEventArgs e)
{
((TreeViewData)e.Node.DataBoundItem).Id = ++id;
}
}

public class TreeViewData
{
public int Id { get; set; }

public int ParentId { get; set; }

public string Name { get; set; }
}