How to implement drag&drop in TreeView among TreeViewItems using Interactivity.Behavior class in WPF

I have implemented the Drag&Drop from a ListBox to a TreeView, as displayed below.
Drag&Drop from ListBox to TreeView

Now I want, for example, after dropping items to TreeView, I can drag ListBoxItem2 and drop it in the TreeViewItem1 and vice versa for other two items. I am using Behavior class from System.Windows.Interactivity library to realize such feature.

Here is the code

XAML:

<Window x:Class="TreeviewExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:local="clr-namespace:TreeviewExample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <GroupBox Header="Features" Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Margin="10,10,10,10">
            <!-- Features ListBox -->
            <ListBox x:Name="featureListBox"
         AllowDrop="True"
         ItemsSource="{Binding ListBoxSource}" Margin="5,10,5,10">
                <i:Interaction.Behaviors>
                    <local:DragDropBehavior />
                </i:Interaction.Behaviors>
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Margin="0,2">
                            <TextBlock Text="{Binding ItemName}" />
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </GroupBox>

        <GroupBox Header="Template" Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" Margin="10,10,10,10">
            <Grid>
                <TreeView Name="templateTreeview"
       ItemsSource="{Binding TreeViewSource}"
       FontSize="14"
       AllowDrop="True" Margin="5,5,5,10">
                    <i:Interaction.Behaviors>
                        <local:DragDropBehavior />
                    </i:Interaction.Behaviors>
                    <TreeView.ItemContainerStyle>
                        <Style TargetType="{x:Type TreeViewItem}">
                            <Setter Property="IsExpanded" Value="True" />
                        </Style>
                    </TreeView.ItemContainerStyle>
                    <TreeView.Resources>
                        <HierarchicalDataTemplate DataType="{x:Type local:ViewItem}" ItemsSource="{Binding Path=ViewItems}">
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Text="{Binding Path=ItemName}" Margin="0,0,10,0" />
                            </StackPanel>
                        </HierarchicalDataTemplate>
                    </TreeView.Resources>
                </TreeView>
            </Grid>
        </GroupBox>
    </Grid>
</Window>

ViewModel:

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;

namespace TreeviewExample
{
    public class MainViewModel
    {
        public ObservableCollection<ViewItem> ListBoxSource { get; set; }
        public ObservableCollection<ViewItem> TreeViewSource { get; set; }

        public MainViewModel()
        {
            ListBoxSource = new ObservableCollection<ViewItem>();
            TreeViewSource = new ObservableCollection<ViewItem>();

            ListBoxSource.Add(new ViewItem("ListBoxItem1"));
            ListBoxSource.Add(new ViewItem("ListBoxItem2"));

            ViewItem defalutView = new ViewItem("Root", 1);
            defalutView.ViewItems.Add(new ViewItem("TreeViewItem1"));
            defalutView.ViewItems.Add(new ViewItem("TreeViewItem2"));
            TreeViewSource.Add(defalutView);
        }
    }

    public class DragDropBehavior : Behavior<UIElement>
    {
        protected override void OnAttached()
        {
            AssociatedObject.PreviewMouseLeftButtonDown += PreviewMouseLeftButtonDown;
            AssociatedObject.Drop += Tv_Drop;
        }

        private void PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (sender is ListBox)
            {
                ListBox dragSource = (ListBox)sender;
                object data = GetDataFromSourceControl(dragSource, e.GetPosition(AssociatedObject));
                if (data != null) DragDrop.DoDragDrop(dragSource, data, DragDropEffects.Move);
            }
        }

        private static object GetDataFromSourceControl(ItemsControl source, Point point)
        {
            UIElement element = source.InputHitTest(point) as UIElement;
            if (element != null)
            {
                object data = DependencyProperty.UnsetValue;
                while (data == DependencyProperty.UnsetValue)
                {
                    data = source.ItemContainerGenerator.ItemFromContainer(element);
                    if (data == DependencyProperty.UnsetValue) element = VisualTreeHelper.GetParent(element) as UIElement;
                    if (element == source) return null;
                }
                if (data != DependencyProperty.UnsetValue) return data;
            }
            return null;
        }

        private void Tv_Drop(object sender, DragEventArgs e)
        {
            if (sender is TreeView)
            {
                e.Effects = DragDropEffects.None;
                e.Handled = true;
                // Verify that this is a valid drop and then store the drop target
                ViewItem targetItem = (e.OriginalSource as TextBlock)?.DataContext as ViewItem;
                MainViewModel vm = (sender as TreeView).DataContext as MainViewModel;
                if (targetItem != null && vm != null)
                {
                    object data = e.Data.GetData(typeof(ViewItem));
                    ViewItem dragData = (ViewItem)data;
                    targetItem.ViewItems.Add(new ViewItem(dragData.ItemName));
                }
            }
        }
    }
}

Model:

using System.Collections.ObjectModel;

namespace TreeviewExample
{
    public class ViewItem
    {
        public string ItemName { get; set; }
        public ObservableCollection<ViewItem> ViewItems { get; } = new ObservableCollection<ViewItem>();
        public int IsVisible { get; set; }

        public ViewItem()
        { }

        public ViewItem(string name, int IsVisible = 0)
        {
            this.ItemName = name;
            this.IsVisible = IsVisible;
        }
    }
}

Questions:

  1. How to enable my desired feature that move items inside TreeView?
  2. What is the difference of DragDropEffects? I also try to set e.Effects to DragDropEffects.Move, but the result looks the same to me.
  3. What is the difference between MouseLeftButtonDown and PreviewMouseLeftButtonDown. I see lots of similar events that with or without Preview.

Could someone help me with the issue?

What I have tried:

In the PreviewMouseLeftButtonDown function, I add another case for TreeView. But I fail to get the drag data. The return value of e.Data.GetData(typeof(ViewItem)) in the Tv_Drop function is the root in the TreeView.

What I expect:

I just want to work inside the DragDropBehavior class to implement the feature that drag and droo items insdie the TreeView.

Leave a Comment