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

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

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;
}
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;
}
}
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);
}
}
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;
}
}
}