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

Implement Copy Drag

The purpose of this tutorial is to show you how to implement "Copy Drag".

Copy Item When Dragging from One TreeView to Another

Using the new DragDrop mode, you can control the DropAction of a drop operation. You can do so through the TreeViewDragDropOptions object as described in the DragDrop tutorial.

In this section you will see how to implement copy drag, when dragging items from one data-bound treeview to another. On the next figure you can see the initial staging. Rad Tree View-DnDCopy Tree To Tree

There are two treeviews populated with some hierarchical data. On the left side is the source treeview (the one from which the items will be copied). On the right side is the target treeview (the one in which the items will be dropped).

Both RadTreeViews are data bound to a collection of business objects. For more information, read the Binding to Object topic.

Here is the initial XAML:

<Grid> 
    <Grid.Resources> 
        <sampleData:RadTreeViewSampleData x:Key="DataSource" /> 
       <sampleData:RadTreeViewSecondSampleData x:Key="SecondDataSource" /> 
 
        <DataTemplate x:Key="Team"> 
            <TextBlock Text="{Binding Name}" /> 
        </DataTemplate> 
        <HierarchicalDataTemplate x:Key="Division" 
                                    ItemTemplate="{StaticResource Team}" 
                                    ItemsSource="{Binding Teams}"> 
            <TextBlock Text="{Binding Name}" /> 
        </HierarchicalDataTemplate> 
        <HierarchicalDataTemplate x:Key="League" 
                                    ItemTemplate="{StaticResource Division}" 
                                    ItemsSource="{Binding Divisions}"> 
            <TextBlock Text="{Binding Name}" /> 
        </HierarchicalDataTemplate> 
    </Grid.Resources> 
    <Grid.ColumnDefinitions> 
        <ColumnDefinition /> 
        <ColumnDefinition /> 
    </Grid.ColumnDefinitions> 
    <telerik:RadTreeView x:Name="xTreeView" 
                            Margin="8" 
                            IsDragDropEnabled="True" 
                            ItemTemplate="{StaticResource League}" 
                            ItemsSource="{Binding Source={StaticResource DataSource}, 
                                                Path=LeaguesDataSource}" /> 
    <telerik:RadTreeView x:Name="radTreeView2" 
                            Grid.Column="1" 
                            Margin="8" 
                            IsDragDropEnabled="True" 
                            ItemTemplate="{StaticResource League}" 
                            ItemsSource="{Binding Source={StaticResource SecondDataSource}, 
                                                Path=DataSource}" /> 
</Grid> 

In the current situation if you try to drag and drop from the left treeview to the right, the items will be moved (not copied). The same is valid if you try to drag and drop from the right treeview to the left. And in order to change that logic and implement a copy drag operation from the left to the right RadTreeView you need to:

  • Attach a DragDropManager DragOver event handler for the right RadTreeView:

        DragDropManager.AddDragOverHandler(radTreeView2, OnDragOver, true); 
    
            DragDropManager.AddDragOverHandler(radTreeView2, OnDragOver, True) 
    

    RadTreeView handles internally the DragDropManager events and in order to invoke a custom handler, you need to explicitly specify that you're adding a handler that should be invoked even for already handled events. This is done through the last - bool argument of the DragDropManager.AddDragOverHandler extension method.

  • In the event handler you should use the following code:

        private void OnDragOver(object sender, Telerik.Windows.DragDrop.DragEventArgs e) 
        { 
            var options = DragDropPayloadManager.GetDataFromObject(e.Data, TreeViewDragDropOptions.Key) as TreeViewDragDropOptions; 
            if (options != null) 
            { 
                options.DropAction = DropAction.Copy; 
            } 
        } 
    
            Private Sub OnDragOver(sender As Object, e As Telerik.Windows.DragDrop.DragEventArgs) 
                Dim options = TryCast(DragDropPayloadManager.GetDataFromObject(e.Data, TreeViewDragDropOptions.Key), TreeViewDragDropOptions) 
                If options IsNot Nothing AndAlso TypeOf options.DropTargetItem.Items Is League Then 
                    options.DropAction = DropAction.Copy 
                End If 
            End Sub 
    

Here is the final result: Rad Tree View-DnDCopy Tree Result

Now if you try to drag an item from the right RadTreeView and drop it in the left tree, the item will be moved. This is due to the fact that we changed the DropAction only while dragging over items of the right RadTreeView. If you need to implement a copy operation when dropping into the left RadTreeView, you'll have to attach an event handler for its DragDropManager DragOver event as well:

DragDropManager.AddDragOverHandler(xTreeView, OnDragOver, true) 
    DragDropManager.AddDragOverHandler(xTreeView, OnDragOver, True) 

If you try to implement the above approach on two declaratively defined RadTreeView controls, you'll encounter an exception. This is due to the fact that the copy operation will try to add one object instance in two different RadTreeView controls. And if you're working with visual objects, then this would raise an exception as you can't use the same visual object multiple times in the VisualTree of the application. Therefore in this case, you'll have to implement a custom copy operation that creates a new object copying the settings of the dragged RadTreeViewItem.

Copy Item When Dragging Within the Same TreeView

In this section you will see how to implement copy drag, when dragging items within the same RadTreeView. On the next figure you can see the initial staging. WPF RadTreeView Copy Item When Dragging Within the Same TreeView

Here is a treeview populated with some hierarchical data. This is the initial XAML declaration:

<Grid> 
    <Grid.Resources> 
        <sampleData:RadTreeViewSampleData x:Key="DataSource" /> 
 
        <DataTemplate x:Key="Team"> 
            <TextBlock Text="{Binding Name}" /> 
        </DataTemplate> 
        <HierarchicalDataTemplate x:Key="Division" 
                                  ItemTemplate="{StaticResource Team}" 
                                  ItemsSource="{Binding Teams}"> 
            <TextBlock Text="{Binding Name}" /> 
        </HierarchicalDataTemplate> 
        <HierarchicalDataTemplate x:Key="League" 
                                  ItemTemplate="{StaticResource Division}" 
                                  ItemsSource="{Binding Divisions}"> 
            <TextBlock Text="{Binding Name}" /> 
        </HierarchicalDataTemplate> 
    </Grid.Resources> 
 
    <telerik:RadTreeView x:Name="xTreeView" 
                         Margin="8" 
                         IsDragDropEnabled="True" 
                         ItemTemplate="{StaticResource League}" 
                         ItemsSource="{Binding Source={StaticResource DataSource}, 
                                               Path=LeaguesDataSource}" /> 
</Grid> 

The RadTreeView is data bound to a collection of business objects. For more information, read the Binding to Object topic.

Before setting a copy DropAction within one RadTreeView, you need to consider the fact that in the RadTreeView once you add the same instance of an object, all item manipulation operations will be applied on every instance of the object found within the RadTreeView control. This basically means that if you follow the approach described in the previous section of the article, you will get multiple RadTreeViewItems wrapping the same object instance. Once you do so, you'll have to work with all items as one as the RadTreeView can't differentiate them. If this is something you;d like to avoid, then you'll have to reconfigure the default RadTreeView drag/drop operation to make a real copy of the dragged item and drop the copy.

In order to implement a real copy drag operation, you need to perform the following steps:

  • Attach DragDropManager Drop and DragDropCompleted handlers on the RadTreeView:

        DragDropManager.AddDropHandler(xTreeView, OnDrop, true); 
        DragDropManager.AddDragDropCompletedHandler(xTreeView, OnDragDropCompleted, true); 
    
            DragDropManager.AddDropHandler(xTreeView, OnDrop, True) 
            DragDropManager.AddDragDropCompletedHandler(xTreeView, OnDragDropCompleted, True) 
    

    RadTreeView handles internally the DragDropManager events and in order to invoke a custom handler, you need to explicitly specify that you're adding a handler that should be invoked even for already handled events. This is done through the last - bool argument of the DragDropManager.AddDragOverHandler extension method.

  • In the OnDrop event handler you should stop the drop operation. You can do so by setting the DropAction to None:

        private void OnDrop(object sender, Telerik.Windows.DragDrop.DragEventArgs e) 
        { 
            var options = DragDropPayloadManager.GetDataFromObject(e.Data, TreeViewDragDropOptions.Key) as TreeViewDragDropOptions; 
            if (options != null) 
            { 
                options.DropAction = DropAction.None; 
            } 
        } 
    
        Private Sub OnDrop(sender As Object, e As Telerik.Windows.DragDrop.DragEventArgs) 
            Dim options = TryCast(DragDropPayloadManager.GetDataFromObject(e.Data, TreeViewDragDropOptions.Key), TreeViewDragDropOptions) 
            If options IsNot Nothing Then 
                options.DropAction = DropAction.None 
            End If 
        End Sub 
    
  • In the OnDragDropCompleted event handler you should implement a custom drop operation. However, as this drop operation will have to create a real copy of the dragged item, you will need to create methods to copy your objects. For example, here are sample methods which copy respectively the Team, the Division and the League objects:

        private Team CopyTeam(Team team) 
        { 
            return new Team(team.Name); 
        } 
     
        private Division CopyDivision(Division division) 
        { 
            Division copyDivision = new Division(division.Name); 
            foreach (Team team in division.Teams) 
            { 
                copyDivision.Teams.Add(this.CopyTeam(team)); 
            } 
            return copyDivision; 
        } 
     
        private League CopyLeague(League league) 
        { 
            League copyLeague = new League(league.Name); 
            foreach (Division division in league.Divisions) 
            { 
                copyLeague.Divisions.Add(this.CopyDivision(division)); 
            } 
            return copyLeague; 
        } 
    
        Private Function CopyTeam(team As Team) As Team 
            Return New Team(team.Name) 
        End Function 
     
        Private Function CopyDivision(division As Division) As Division 
            Dim div As New Division(division.Name) 
            For Each team As Team In division.Teams 
                div.Teams.Add(Me.CopyTeam(team)) 
            Next 
            Return div 
        End Function 
     
        Private Function CopyLeague(league As League) As League 
            Dim l As New League(league.Name) 
            For Each division As Division In league.Divisions 
                l.Divisions.Add(Me.CopyDivision(division)) 
            Next 
            Return l 
        End Function 
    
  • Next, you need to implement a custom drop operation in the OnDragDropCompleted event handler. This means that you need to define a logic that tracks the type of the dragged item as well as the type of the drop destination to make sure that the drop is actually allowed. This logic will also have to track the DropPosition to decide where to insert the real copy of the dragged item.

        private void OnDragDropCompleted(object sender, DragDropCompletedEventArgs e) 
        { 
            var options = DragDropPayloadManager.GetDataFromObject(e.Data, TreeViewDragDropOptions.Key) as TreeViewDragDropOptions; 
            if (options != null) 
            { 
                var item = options.DraggedItems.FirstOrDefault(); 
                if (options.DropTargetItem != null) 
                { 
                    if (item is Team) 
                    { 
                        if (options.DropPosition == Telerik.Windows.Controls.DropPosition.Inside && options.DropTargetItem.Item is Division) 
                        { 
                            (options.DropTargetItem.Item as Division).Teams.Add(CopyTeam((Team)item)); 
                        } 
                        else if (options.DropPosition != Telerik.Windows.Controls.DropPosition.Inside && options.DropTargetItem.Item is Team) 
                        { 
                            Division parentDivision = options.DropTargetItem.ParentItem.Item as Division; 
                            switch (options.DropPosition) 
                            { 
                                case Telerik.Windows.Controls.DropPosition.After: 
                                    parentDivision.Teams.Insert(options.DropTargetItem.Index + 1, CopyTeam((Team)item)); 
                                    break; 
                                case Telerik.Windows.Controls.DropPosition.Before: 
                                    parentDivision.Teams.Insert(options.DropTargetItem.Index, CopyTeam((Team)item)); 
                                    break; 
                            } 
                        } 
                    } 
                    else if (item is Division) 
                    { 
                        if (options.DropPosition == Telerik.Windows.Controls.DropPosition.Inside && options.DropTargetItem.Item is League) 
                        { 
                            (options.DropTargetItem.Item as League).Divisions.Add(CopyDivision((Division)item)); 
                        } 
                        else if (options.DropPosition != Telerik.Windows.Controls.DropPosition.Inside && options.DropTargetItem.Item is Division) 
                        { 
                            League parentLeague = options.DropTargetItem.ParentItem.Item as League; 
                            switch (options.DropPosition) 
                            { 
                                case Telerik.Windows.Controls.DropPosition.After: 
                                    parentLeague.Divisions.Insert(options.DropTargetItem.Index + 1, CopyDivision((Division)item)); 
                                    break; 
                                case Telerik.Windows.Controls.DropPosition.Before: 
                                    parentLeague.Divisions.Insert(options.DropTargetItem.Index, CopyDivision((Division)item)); 
                                    break; 
                            } 
                        } 
                    } 
                    else if (item is League) 
                    { 
                        if (options.DropTargetItem == null && options.DropTargetTree != null) 
                        { 
                            (options.DropTargetTree.ItemsSource as IList).Add(CopyLeague((League)item)); 
                        } 
                        else if (options.DropPosition != Telerik.Windows.Controls.DropPosition.Inside && options.DropTargetItem.Item is League) 
                        { 
                            IList source = options.DropTargetItem.ParentTreeView.ItemsSource as IList; 
                            switch (options.DropPosition) 
                            { 
                                case Telerik.Windows.Controls.DropPosition.After: 
                                    source.Insert(options.DropTargetItem.Index + 1, CopyLeague((League)item)); 
                                    break; 
                                case Telerik.Windows.Controls.DropPosition.Before: 
                                    source.Insert(options.DropTargetItem.Index, CopyLeague((League)item)); 
                                    break; 
                            } 
                        } 
                    } 
     
                } 
            } 
        } 
    
            Private Sub OnDragDropCompleted(sender As Object, e As DragDropCompletedEventArgs) 
            Dim options = TryCast(DragDropPayloadManager.GetDataFromObject(e.Data, TreeViewDragDropOptions.Key), TreeViewDragDropOptions) 
            If options IsNot Nothing Then 
                Dim item = options.DraggedItems.FirstOrDefault() 
                If options.DropTargetItem IsNot Nothing Then 
                    If TypeOf item Is Team Then 
                        If options.DropPosition = Telerik.Windows.Controls.DropPosition.Inside AndAlso TypeOf options.DropTargetItem.Item Is Division Then 
                            TryCast(options.DropTargetItem.Item, Division).Teams.Add(CopyTeam(DirectCast(item, Team))) 
                        ElseIf options.DropPosition <> Telerik.Windows.Controls.DropPosition.Inside AndAlso TypeOf options.DropTargetItem.Item Is Team Then 
                            Dim parentDivision As Division = TryCast(options.DropTargetItem.ParentItem.Item, Division) 
                            Select Case options.DropPosition 
                                Case Telerik.Windows.Controls.DropPosition.After 
                                    parentDivision.Teams.Insert(options.DropTargetItem.Index + 1, CopyTeam(DirectCast(item, Team))) 
                                    Exit Select 
                                Case Telerik.Windows.Controls.DropPosition.Before 
                                    parentDivision.Teams.Insert(options.DropTargetItem.Index, CopyTeam(DirectCast(item, Team))) 
                                    Exit Select 
                            End Select 
                        End If 
                    ElseIf TypeOf item Is Division Then 
                        If options.DropPosition = Telerik.Windows.Controls.DropPosition.Inside AndAlso TypeOf options.DropTargetItem.Item Is League Then 
                            TryCast(options.DropTargetItem.Item, League).Divisions.Add(CopyDivision(DirectCast(item, Division))) 
                        ElseIf options.DropPosition <> Telerik.Windows.Controls.DropPosition.Inside AndAlso TypeOf options.DropTargetItem.Item Is Division Then 
                            Dim parentLeague As League = TryCast(options.DropTargetItem.ParentItem.Item, League) 
                            Select Case options.DropPosition 
                                Case Telerik.Windows.Controls.DropPosition.After 
                                    parentLeague.Divisions.Insert(options.DropTargetItem.Index + 1, CopyDivision(DirectCast(item, Division))) 
                                    Exit Select 
                                Case Telerik.Windows.Controls.DropPosition.Before 
                                    parentLeague.Divisions.Insert(options.DropTargetItem.Index, CopyDivision(DirectCast(item, Division))) 
                                    Exit Select 
                            End Select 
                        End If 
                    ElseIf TypeOf item Is League Then 
                        If options.DropTargetItem Is Nothing AndAlso options.DropTargetTree IsNot Nothing Then 
                            TryCast(options.DropTargetTree.ItemsSource, IList).Add(CopyLeague(DirectCast(item, League))) 
                        ElseIf options.DropPosition <> Telerik.Windows.Controls.DropPosition.Inside AndAlso TypeOf options.DropTargetItem.Item Is League Then 
                            Dim source As IList = TryCast(options.DropTargetItem.ParentTreeView.ItemsSource, IList) 
                            Select Case options.DropPosition 
                                Case Telerik.Windows.Controls.DropPosition.After 
                                    source.Insert(options.DropTargetItem.Index + 1, CopyLeague(DirectCast(item, League))) 
                                    Exit Select 
                                Case Telerik.Windows.Controls.DropPosition.Before 
                                    source.Insert(options.DropTargetItem.Index, CopyLeague(DirectCast(item, League))) 
                                    Exit Select 
                            End Select 
                        End If 
     
                    End If 
                End If 
            End If 
        End Sub      
    

With this the real copy DragDrop implementation is ready. The final result can be seen on the next snapshots.

Copy Team: Rad Tree View-DnDCopy Team

Copy Division: Rad Tree View-DnDCopy Division

Copy League: Rad Tree View-DnDCopy League

You can further customize this solution by applying a logic that determines the DropAction based on the type of the item the drag operation is currently over:

private void OnDrop(object sender, Telerik.Windows.DragDrop.DragEventArgs e) 
{ 
    var options = DragDropPayloadManager.GetDataFromObject(e.Data, TreeViewDragDropOptions.Key) as TreeViewDragDropOptions; 
    if (options != null) 
    { 
        options.DropAction = DropAction.None; 
    } 
} 
Private Sub OnDrop(sender As Object, e As Telerik.Windows.DragDrop.DragEventArgs) 
    Dim options = TryCast(DragDropPayloadManager.GetDataFromObject(e.Data, TreeViewDragDropOptions.Key), TreeViewDragDropOptions) 
    If options IsNot Nothing Then 
        options.DropAction = DropAction.None 
    End If 
End Sub 

See Also

In this article