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.
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
0
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.
I hope this information helps. Should you have further questions, I would be glad to help.
Regards,
Desislava
Telerik
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.