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

Drag and Drop in bound mode

When RadTreeView is in bound mode, it supports a basic drag and drop behavior. The dragged node is inserted at the last position in its parent.

Figure 1: Default drag and drop behavior in bound mode

WinForms RadTreeView Default drag and drop behavior in bound mode

In order to enable this functionality, you should set the AllowDragDrop property to true. However, due to the specificity of the RadTreeView’s data binding and the set up hierarchical data structure, it is necessary to handle manually the drag and drop operation to obtain correct nodes order.

Figure 2: Custom drag and drop behavior in bound mode

WinForms RadTreeView Custom drag and drop behavior in bound mode

For this purpose, it is necessary to create a custom TreeViewDragDropService. This article demonstrates a sample approach how to achieve it.

1. Consider the RadTreeView is bound to the following self-referencing data.


protected void BindRadTreeView()
{
    DataTable dt = new DataTable();
    dt.Columns.Add("Id", typeof(string));
    dt.Columns.Add("Title", typeof(string));
    dt.Columns.Add("ParentId", typeof(string));

    string parentId = string.Empty;
    string childId = string.Empty;
    for (int i = 0; i < 3; i++)
    {
        parentId = Guid.NewGuid().ToString();
        dt.Rows.Add(parentId, "Node" + i, null);
        for (int j = 0; j < 5; j++)
        {
            childId = Guid.NewGuid().ToString();
            dt.Rows.Add(childId, "SubNode" + i + "." + j, parentId);
        }
    }

    this.radTreeView1.ChildMember = "Id";
    this.radTreeView1.ParentMember = "ParentId";
    this.radTreeView1.DisplayMember = "Title";
    this.radTreeView1.DataSource = dt;
}

Protected Sub BindRadTreeView()
    Dim dt As New DataTable()
    dt.Columns.Add("Id", GetType(String))
    dt.Columns.Add("Title", GetType(String))
    dt.Columns.Add("ParentId", GetType(String))
    Dim parentId As String = String.Empty
    Dim childId As String = String.Empty
    For i As Integer = 0 To 2
        parentId = Guid.NewGuid().ToString()
        dt.Rows.Add(parentId, "Node" & i, Nothing)
        For j As Integer = 0 To 4
            childId = Guid.NewGuid().ToString()
            dt.Rows.Add(childId, "SubNode" & i & "." & j, parentId)
        Next
    Next
    Me.radTreeView1.ChildMember = "Id"
    Me.radTreeView1.ParentMember = "ParentId"
    Me.radTreeView1.DisplayMember = "Title"
    Me.radTreeView1.DataSource = dt
End Sub

2. Enable multiple selection by setting the MultiSelect property to true.

3. Create a derivative of the TreeViewDragDropService which should perform the desired drag and drop functionality. The OnPreviewDragOver method allows you to control on what targets the nodes, being dragged, can be dropped on. The OnPreviewDragDrop method initiates the actual physical move of the nodes from one position to another.


class CustomDragDropService : TreeViewDragDropService
{
    private RadTreeViewElement owner;
    private List<RadTreeNode> draggedNodes;

    public CustomDragDropService(RadTreeViewElement owner) : base(owner)
    {
        this.owner = owner;
    }

    //Save the dragged nodes
    protected override void PerformStart()
    {
        base.PerformStart();

        draggedNodes = new List<RadTreeNode>();
        foreach (RadTreeNode selectedNode in this.owner.SelectedNodes)
        {
            draggedNodes.Add(selectedNode);
        }
    }

    //Clean the saved nodes
    protected override void PerformStop()
    {
        base.PerformStop();
        draggedNodes.Clear();
    }

    //If tree element or node element is hovered, allow drop
    protected override void OnPreviewDragOver(RadDragOverEventArgs e)
    {
        base.OnPreviewDragOver(e);

        RadTreeViewElement targetElement = e.HitTarget as RadTreeViewElement;
        if (targetElement != null && targetElement.Equals(this.owner))
        {
            e.CanDrop = true;
        }
        else if (e.HitTarget is TreeNodeElement)
        {
            e.CanDrop = true;

            foreach (RadTreeNode draggedNode in draggedNodes)
            {
                RadTreeNode targetNode = (e.HitTarget as TreeNodeElement).Data.Parent;
                if (draggedNode == targetNode)
                {
                    //prevent dragging of a parent node over some of its child nodes
                    e.CanDrop = false;
                    break;
                }
            }
        }
    }

    //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)
        {
            return;
        }

        targetTreeView.BeginUpdate();

        foreach (RadTreeNode node in draggedNodes)
        {
            DataRowView rowView = node.DataBoundItem as DataRowView;
            DataRow row = rowView.Row;
            DataTable dt = targetTreeView.DataSource as DataTable;
            if (dt == null)
            {
                return;                        
            }

            //save expanded state and vertical scrollbar value                   

            if (targetNodeElement != null)
            {
                if (CanShowDropHint(Cursor.Position, targetNodeElement))
                {
                    //change node' parent
                    RadTreeNode nodeOver = targetNodeElement.Data.Parent;

                    if (nodeOver == null)
                    {
                        nodeOver = targetNodeElement.Data;
                    }

                    DataRowView targetRowView = (DataRowView)nodeOver.DataBoundItem;
                    DataRow targetRow = targetRowView.Row;
                    if (row["ParentId"] != targetRow["ParentId"])
                    {
                        row["ParentId"] = targetRow["Id"];
                    }

                    DataRow rowToInsert = dt.NewRow();
                    rowToInsert["ParentId"] = row["ParentId"];
                    rowToInsert["Id"] = row["Id"];
                    rowToInsert["Title"] = row["Title"];
                    dt.Rows.Remove(row);
                    int targetIndex = GetTargetIndex(dt, targetNodeElement);

                    DropPosition position = this.GetDropPosition(e.DropLocation, targetNodeElement);
                    if (position == DropPosition.AfterNode)
                    {
                        targetIndex++;
                    }
                    dt.Rows.InsertAt(rowToInsert, targetIndex);
                }
                else
                {
                    RadTreeNode nodeOver = targetNodeElement.Data;
                    DataRowView targetRowView = (DataRowView)nodeOver.DataBoundItem;
                    DataRow targetRow = targetRowView.Row;
                    row["ParentId"] = targetRow["Id"];
                }
            }
            else
            {
                //make the node "root node"
                row["ParentId"] = null;
            }

            object ds = targetTreeView.DataSource;
            targetTreeView.DataSource = null;
            targetTreeView.DataSource = ds;

            targetTreeView.Update(RadTreeViewElement.UpdateActions.ItemAdded);
            //restore expanded state and vertical scrollbar value
        }
        targetTreeView.EndUpdate();
    }

    private int GetTargetIndex(DataTable dt, TreeNodeElement targetNodeElement)
    {
        int index = 0;
        DataRowView targetRowView = (DataRowView)targetNodeElement.Data.DataBoundItem;
        DataRow targetRow = targetRowView.Row;
        index = dt.Rows.IndexOf(targetRow);

        return index;
    }

    private bool CanShowDropHint(Point point, TreeNodeElement nodeElement)
    {
        if (nodeElement == null)
        {
            return false;
        }

        Point client = nodeElement.PointFromScreen(point);
        int part = nodeElement.Size.Height / 3;
        return client.Y < part || client.Y > part * 2;
    }
}

Private Class CustomDragDropService
    Inherits TreeViewDragDropService
    Private owner As RadTreeViewElement
    Private draggedNodes As List(Of RadTreeNode)
    Public Sub New(owner As RadTreeViewElement)
        MyBase.New(owner)
        Me.owner = owner
    End Sub
    'Save the dragged nodes
    Protected Overrides Sub PerformStart()
        MyBase.PerformStart()
        draggedNodes = New List(Of RadTreeNode)()
        For Each selectedNode As RadTreeNode In Me.owner.SelectedNodes
            draggedNodes.Add(selectedNode)
        Next
    End Sub
    'Clean the saved nodes
    Protected Overrides Sub PerformStop()
        MyBase.PerformStop()
        draggedNodes.Clear()
    End Sub
    'If tree element or node element is hovered, allow drop
    Protected Overrides Sub OnPreviewDragOver(e As RadDragOverEventArgs)
        MyBase.OnPreviewDragOver(e)
        Dim targetElement As RadTreeViewElement = TryCast(e.HitTarget, RadTreeViewElement)
        If targetElement IsNot Nothing AndAlso targetElement.Equals(Me.owner) Then
            e.CanDrop = True
        ElseIf TypeOf e.HitTarget Is TreeNodeElement Then
            e.CanDrop = True
            For Each draggedNode As RadTreeNode In draggedNodes
                Dim targetNode As RadTreeNode = TryCast(e.HitTarget, TreeNodeElement).Data.Parent
                If draggedNode.Equals(targetNode) Then
                    'prevent dragging of a parent node over some of its child nodes
                    e.CanDrop = False
                    Exit For
                End If
            Next
        End If
    End Sub
    'Create copies of the selected node(s) and add them to the hovered node/tree
    Protected Overrides Sub OnPreviewDragDrop(e As RadDropEventArgs)
        Dim targetNodeElement As TreeNodeElement = TryCast(e.HitTarget, TreeNodeElement)
        Dim targetTreeView As RadTreeViewElement = If(targetNodeElement Is Nothing, TryCast(e.HitTarget, RadTreeViewElement), targetNodeElement.TreeViewElement)
        If targetTreeView Is Nothing Then
            Return
        End If
        targetTreeView.BeginUpdate()
        For Each node As RadTreeNode In draggedNodes
            Dim rowView As DataRowView = TryCast(node.DataBoundItem, DataRowView)
            Dim row As DataRow = rowView.Row
            Dim dt As DataTable = TryCast(targetTreeView.DataSource, DataTable)
            If dt Is Nothing Then
                Return
            End If
            'save expanded state and vertical scrollbar value                   
            If targetNodeElement IsNot Nothing Then
                If CanShowDropHint(Cursor.Position, targetNodeElement) Then
                    'change node' parent
                    Dim nodeOver As RadTreeNode = targetNodeElement.Data.Parent
                    If nodeOver Is Nothing Then
                        nodeOver = targetNodeElement.Data
                    End If
                    Dim targetRowView As DataRowView = DirectCast(nodeOver.DataBoundItem, DataRowView)
                    Dim targetRow As DataRow = targetRowView.Row
                    If Not row("ParentId").Equals(targetRow("ParentId")) Then
                        row("ParentId") = targetRow("Id")
                    End If
                    Dim rowToInsert As DataRow = dt.NewRow()
                    rowToInsert("ParentId") = row("ParentId")
                    rowToInsert("Id") = row("Id")
                    rowToInsert("Title") = row("Title")
                    dt.Rows.Remove(row)
                    Dim targetIndex As Integer = GetTargetIndex(dt, targetNodeElement)
                    Dim position As DropPosition = Me.GetDropPosition(e.DropLocation, targetNodeElement)
                    If position = DropPosition.AfterNode Then
                        targetIndex += 1
                    End If
                    dt.Rows.InsertAt(rowToInsert, targetIndex)
                Else
                    Dim nodeOver As RadTreeNode = targetNodeElement.Data
                    Dim targetRowView As DataRowView = DirectCast(nodeOver.DataBoundItem, DataRowView)
                    Dim targetRow As DataRow = targetRowView.Row
                    row("ParentId") = targetRow("Id")
                End If
            Else
                'make the node "root node"
                row("ParentId") = Nothing
            End If
            Dim ds As Object = targetTreeView.DataSource
            targetTreeView.DataSource = Nothing
            targetTreeView.DataSource = ds
            'restore expanded state and vertical scrollbar value
            targetTreeView.Update(RadTreeViewElement.UpdateActions.ItemAdded)
        Next
        targetTreeView.EndUpdate()
    End Sub
    Private Function GetTargetIndex(dt As DataTable, targetNodeElement As TreeNodeElement) As Integer
        Dim index As Integer = 0
        Dim targetRowView As DataRowView = DirectCast(targetNodeElement.Data.DataBoundItem, DataRowView)
        Dim targetRow As DataRow = targetRowView.Row
        index = dt.Rows.IndexOf(targetRow)
        Return index
    End Function
    Private Function CanShowDropHint(point As Point, nodeElement As TreeNodeElement) As Boolean
        If nodeElement Is Nothing Then
            Return False
        End If
        Dim client As Point = nodeElement.PointFromScreen(point)
        Dim part As Integer = nodeElement.Size.Height / 3
        Return client.Y < part OrElse client.Y > part * 2
    End Function
End Class

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. Keep RadTreeView states on reset help article explains how to save the tree state prior the change and restore it afterwards.

4. The custom TreeViewDragDropService is ready. Now, we need to replace the default one. For this purpose, it is necessary to create a derivative of the RadTreeViewElement and override the CreateDragDropService method.


class CustomTreeViewElement : RadTreeViewElement
{
    protected override Type ThemeEffectiveType
    {
        get
        {
            return typeof(RadTreeViewElement);
        }
    }

    protected override TreeViewDragDropService CreateDragDropService()
    {
        return new CustomDragDropService(this);
    }
}

Private Class CustomTreeViewElement
    Inherits RadTreeViewElement
    Protected Overrides ReadOnly Property ThemeEffectiveType() As Type
        Get
            Return GetType(RadTreeViewElement)
        End Get
    End Property
    Protected Overrides Function CreateDragDropService() As TreeViewDragDropService
        Return New CustomDragDropService(Me)
    End Function
End Class

5. Finally, replace the default RadTreeViewElement in the tree with the custom one.


class CustomTreeView : RadTreeView
{
    protected override RadTreeViewElement CreateTreeViewElement()
    {
        return new CustomTreeViewElement();
    }

    public override string ThemeClassName
    {
        get
        {
            return typeof(RadTreeView).FullName;
        }
        set
        {
            base.ThemeClassName = value;
        }
    }
}

Private Class CustomTreeView
    Inherits RadTreeView
    Protected Overrides Function CreateTreeViewElement() As RadTreeViewElement
        Return New CustomTreeViewElement()
    End Function
    Public Overrides Property ThemeClassName() As String
        Get
            Return GetType(RadTreeView).FullName
        End Get
        Set(value As String)
            MyBase.ThemeClassName = value
        End Set
    End Property
End Class

See Also

In this article