Drop as sibling when DropPosition is AsChildNode

2 posts, 0 answers
  1. John
    John avatar
    3 posts
    Member since:
    Aug 2010

    Posted 01 Aug 2014 Link to this post

    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.
  2. Dess
    Admin
    Dess avatar
    1609 posts

    Posted 05 Aug 2014 Link to this post

    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.
     
  3. UI for WinForms is Visual Studio 2017 Ready
Back to Top