This is a migrated thread and some comments may be shown as answers.

Drop as sibling when DropPosition is AsChildNode

1 Answer 164 Views
Treeview
This is a migrated thread and some comments may be shown as answers.
John
Top achievements
Rank 1
John asked on 01 Aug 2014, 05:04 PM
I am using drag and drop to reorder nodes in a treeview, but there are certain rules around where nodes can be dropped and what should happen.  I am using a BindingList as the datasource.  

The rule that I am trying to support is that a sibling node that has children can be dropped on another sibling that has children, within the same parent.  The dropped sibling should not become a child of the target sibling, but it should maintain its original parent (which is the same as the target's parent).  

How can I accomplish this behavior?   

Thanks for any help.

1 Answer, 1 is accepted

Sort by
0
Dess | Tech Support Engineer, Principal
Telerik team
answered on 05 Aug 2014, 02:18 PM
Hello John,

Thank you for writing.

Please find below a sample code snippet, implementing the described custom drag and drop functionality.

When you use a customized TreeViewDragDropService, overriding the PerformStart method allows you to start the drag and drop service using custom logic, e.g. start dragging only if the current node has child nodes.
Overriding the OnPreviewDragOver method gives you the opportunity to determine if the drop operation is allowed for the target. In the sample the drop operation is allowed only over nodes of the same level and parent and have child nodes.
In the OnPreviewDragDrop method we perform the exact reordering of the objects in the data source. 

RadTreeView is a control that allows you to visualize hierarchical structures of data in the form of a tree. However, when in bound mode and 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.
BindingList<Item> items = new BindingList<Item>();
 
public Form1()
{
    InitializeComponent();
    string parentId;
    string childId;
    Item parentItem;
    Item childItem;
    for (int i = 0; i < 5; i++)
    {
        parentId = Guid.NewGuid().ToString();
        parentItem = new Item(parentId, null, "Node." + i);
        items.Add(parentItem);
        for (int j = 0; j < 5; j++)
        {
            childId = Guid.NewGuid().ToString();
            childItem = new Item(childId,parentId,"ChildNode." + i + "." + j);
            items.Add(childItem);
            if (j % 2 == 0)
            {
                for (int k = 0; k < 3; k++)
                {
                    childItem = new Item(Guid.NewGuid().ToString(),childId,"ChildNode." + i + "." + j + "." + k);
                    items.Add(childItem);
                }
            }
        }
    }
    this.radTreeView1.DisplayMember = "Name";
    this.radTreeView1.ParentMember = "ParentId";
    this.radTreeView1.ChildMember = "Id";
    this.radTreeView1.DataSource = items;
    this.radTreeView1.AllowDragDrop = true;
    this.radTreeView1.ExpandAll();
}
 
public class Item
{
    public string Id { get; set; }
 
    public string ParentId { get; set; }
 
    public string Name { get; set; }
 
    public Item(string id, string parentId, string name)
            {
                this.Id = id;
                this.ParentId = parentId;
                this.Name = name;
            }
}

class CustomTreeView : RadTreeView
{
    //Replace the default element with the custom one
    protected override RadTreeViewElement CreateTreeViewElement()
            {
                return new CustomTreeViewElement();
            }
 
    //Enable theming for the control
    public override string ThemeClassName
            {
                get
                {
                    return typeof(RadTreeView).FullName;
                }
            }
}

class CustomTreeViewElement : RadTreeViewElement
{
    //Enable themeing for the element
    protected override Type ThemeEffectiveType
           {
               get
               {
                   return typeof(RadTreeViewElement);
               }
           }
 
    //Replace the default drag drop service with the custom one
    protected override TreeViewDragDropService CreateDragDropService()
           {
               return new CustomDragDropService(this);
           }
}

class CustomDragDropService : TreeViewDragDropService
{
    RadTreeViewElement owner;
    RadTreeNode draggedNode;
 
    //Initialize the service
    public CustomDragDropService(RadTreeViewElement owner) : base(owner)
    {
        this.owner = owner;
        this.ShowDropHint = false;
    }
 
    //Save the dragged node
    protected override void PerformStart()
    {
        TreeNodeElement draggedNodeElement = this.Context as TreeNodeElement;
        //a sibling node that has children can be dragged
        if (draggedNodeElement.Data.Nodes.Count > 0)
        {
            base.PerformStart();
            this.draggedNode = draggedNodeElement.Data;
        }
    }
 
    //Clean the saved node
    protected override void PerformStop()
    {
        base.PerformStop();
        this.draggedNode = null;
    }
 
    //If tree element is hovered, allow drop
    protected override void OnPreviewDragOver(RadDragOverEventArgs e)
    {
        base.OnPreviewDragOver(e);
 
        TreeNodeElement targetElement = e.HitTarget as TreeNodeElement;
        if (targetElement != null && targetElement.Data != null)
        {
            //a sibling node that has children can be dropped on another sibling that has children, within the same parent.
            if (draggedNode.Level == targetElement.Data.Level && targetElement.Data.Nodes.Count > 0 &&
                draggedNode.Parent == targetElement.Data.Parent)
            {
                e.CanDrop = true;
            }
            else
            {
                e.CanDrop = false;
            }
        }
    }
 
    //Create copies of the selected node(s) and add them to the hovered node/tree
    protected override void OnPreviewDragDrop(RadDropEventArgs e)
    {
        TreeNodeElement targetNodeElement = e.HitTarget as TreeNodeElement;
        RadTreeViewElement targetTreeView = targetNodeElement == null ? e.HitTarget as RadTreeViewElement : targetNodeElement.TreeViewElement;
        
        if (targetTreeView == null || targetNodeElement == null)
        {
            return;
        }
        //keep nodes state
        int scrollBarValue = targetTreeView.VScrollBar.Value;          
        foreach (RadTreeNode nodeToSave in targetTreeView.Nodes)
        {
            SaveExpandedStates(nodeToSave);
        }
        targetTreeView.BeginUpdate();
        this.owner.BeginUpdate();
 
        Item targetItem = targetNodeElement.Data.DataBoundItem as Item;
        Item draggedItem = draggedNode.DataBoundItem as Item;
        BindingList<Item> items = targetTreeView.DataSource as BindingList<Item>;
        if (items != null)
        {
            int targetIndex = items.IndexOf(targetItem);
            int sourceIndex = items.IndexOf(draggedItem);
 
            if (sourceIndex < targetIndex)
            {
                targetIndex--;
            }
            items.Remove(draggedItem);
            if (targetIndex > -1)
            {
                items.Insert(targetIndex, draggedItem);
            }
        }
 
        this.owner.EndUpdate();
        targetTreeView.EndUpdate();
         
        //restore nodes state
        foreach (RadTreeNode nodeToRestore in targetTreeView.Nodes)
        {
            RestoreExpandedStates(nodeToRestore);
        }
        targetTreeView.VScrollBar.Value = scrollBarValue;
    }
     
    //Return a copy of a node
    protected virtual RadTreeNode CreateNewTreeNode(RadTreeNode node)
    {
        return node.Clone() as RadTreeNode;
    }
 
    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);
        }
    }
}

I hope this information helps. Should you have further questions, I would be glad to help.
 
Regards,
Desislava
Telerik
 
Check out Telerik Analytics, the service which allows developers to discover app usage patterns, analyze user data, log exceptions, solve problems and profile application performance at run time. Watch the videos and start improving your app based on facts, not hunches.
 
Tags
Treeview
Asked by
John
Top achievements
Rank 1
Answers by
Dess | Tech Support Engineer, Principal
Telerik team
Share this question
or