Tag Archives: Drag

How to accomplish Drag and Drop WPF TreeViewItem

Someone wants to accomplish drag and drop TreeViewItem in the WPF application, the data is loaded from a XML file and save back to it.

Firstly, we need to accomplish the first requirement-loading data from xaml file and display them in the WPF TreeView control.

XML file:

<?xml version="1.0" encoding="utf-8"?>
<Collection>
  <item Name="Jimmy">
    <Name>Jimmy Chen</Name>
    <Age>25</Age>
    <Gender>Male</Gender>
    <Icon>jimmy.png</Icon>
  </item>
  <item Name="James">
    <Name>James Hoffman</Name>
    <Age>23</Age>
    <Gender>Male</Gender>
    <Icon>james.png</Icon>
  </item>
  <item Name="Franklin">
    <Name>Franklin Chen</Name>
    <Age>22</Age>
    <Gender>Male</Gender>
    <Icon>franklin.png</Icon>
  </item>
</Collection>

Now, we need to load XML data to a ObservableCollection<T> instance:
Family & FamilyMember class:

public class Family : INotifyPropertyChanged
{
        public Family()
        {
            this.Children = new ObservableCollection<FamilyMember>();
        }
        private int _myage;

        public int myAge
        {
            get { return _myage; }
            set
            {
                if (_myage != value)
                {
                    _myage = value;
                    OnPropertyChanged("Age");
                }
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;


        protected void OnPropertyChanged(string p_strPropertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(p_strPropertyName));
        }
        public string myName { get; set; }
        public string myGender { get; set; }
        public string myImg { get; set; }

        public ObservableCollection<FamilyMember> Children { get; set; }
}

public class FamilyMember
{
        public string Name { get; set; }
        public string Age { get; set; }
        public string Img { get; set; }
}

Loading Data:

public ObservableCollection<Family> MyData { get; set; }

public MainWindow()
{
            InitializeComponent();

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(System.IO.Directory.GetCurrentDirectory() + "//FamilyBook.xml");
            XmlNodeList nodeName = xmlDoc.GetElementsByTagName("Name");
            XmlNodeList nodeGender = xmlDoc.GetElementsByTagName("Gender");
            XmlNodeList nodeAge = xmlDoc.GetElementsByTagName("Age");
            XmlNodeList nodeImg = xmlDoc.GetElementsByTagName("Icon");
            XmlNodeList elemList = xmlDoc.GetElementsByTagName("item");
            MyData = new ObservableCollection<Family>();
            for (int i = 0; i < elemList.Count; i++)
            {
                MyData.Add(new Family()
                {
                    myName = nodeName[i].InnerText,
                    myGender = nodeGender[i].InnerText,
                    myAge = Convert.ToInt32(nodeAge[i].InnerText),

                    myImg = System.IO.Directory.GetCurrentDirectory() + "/favicon/" + nodeImg[i].InnerText

                });
            }

            this.DataContext = this;
}

To display data in WPF TreeView, we need to write HierarchicalDataTemplate and DataTemplate:

<TreeView HorizontalAlignment="Left" ItemsSource="{Binding MyData}"  x:Name="trvFamilies"  Background="{x:Null}" >
            <TreeView.Resources>

                <HierarchicalDataTemplate DataType="{x:Type local:Family}"  ItemsSource="{Binding Children}">
                    <StackPanel Orientation="Horizontal">
                        <Image Source="{Binding myImg }" Margin="5,0,5,0" Height="18" Width="18" ></Image>
                        <TextBlock Text="{Binding Path=myName}" Width="167" Padding="0,3,0,0"   TextTrimming="CharacterEllipsis" ></TextBlock>
                        <Label Content="{Binding myAge}" Foreground="#FF515151" Width="39"  HorizontalContentAlignment="Right" Margin="0,0,12,0"></Label>

                    </StackPanel>
                </HierarchicalDataTemplate>
                <DataTemplate DataType="{x:Type local:FamilyMember}"  >
                    <StackPanel Orientation="Horizontal">
                        <Image Source="{Binding Img}" Margin="21,0,5,0" Height="18" Width="18" />
                        <TextBlock  x:Name="surl" Text="{Binding Name}" Width="158" TextTrimming="CharacterEllipsis" Padding="0,3,0,0"     />

                        <Label Content="{Binding Age}" Foreground="#FF515151"  Width="30"   HorizontalContentAlignment="Right"  Margin="0,0,12,0"/>

                    </StackPanel>
                </DataTemplate>
            </TreeView.Resources>
        </TreeView>

So far, the UI should be like this:
2014-09-02_210033

Now, we need to accomplish the most difficult part: Drag and drop TreeViewItem.

Anyway, here is an article on MSDN to talk about Drag and Drop Overview: http://msdn.microsoft.com/en-us/library/ms742859(v=vs.110).aspx

We need to set TreeView’s AllowDrop property to True first and handle the following events:

int flag = 0;
Point startPoint;
private void TV_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
            if (e.OriginalSource is TextBlock)
            {
                // Store the mouse position
                startPoint = e.GetPosition(null);
                flag = 1;
            }
}

private void trvFamilies_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
            flag = 0;
}

private void TV_MouseMove(object sender, MouseEventArgs e)
{
            if (flag == 1) //Begin Drag
            {
                // Get the current mouse position
                Point mousePos = e.GetPosition(null);
                Vector diff = startPoint - mousePos;

                if (e.LeftButton == MouseButtonState.Pressed &&
                    Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
                {
                    // Get the dragged ListViewItem
                    TreeView treeView = sender as TreeView;
                    TreeViewItem treeViewItem =
                        FindAnchestor<TreeViewItem>((DependencyObject)e.OriginalSource);
                    if (treeViewItem != null)
                    {
                        // Find the data behind the ListViewItem
                        Family contact = (Family)treeView.ItemContainerGenerator.ItemFromContainer(treeViewItem);

                        // Initialize the drag & drop operation
                        DataObject dragData = new DataObject("myFormat", contact);
                        DragDrop.DoDragDrop(treeViewItem, dragData, DragDropEffects.Move);
                    }
                }
            }
}

// Helper to search up the VisualTree
private static T FindAnchestor<T>(DependencyObject current)
            where T : DependencyObject
{
            do
            {
                if (current is T)
                {
                    return (T)current;
                }
                current = VisualTreeHelper.GetParent(current);
            }
            while (current != null);
            return null;
}

private void FindDropTarget(TreeView tv, out TreeViewItem pItemNode, DragEventArgs pDragEventArgs)
{
            pItemNode = null;

            DependencyObject k = VisualTreeHelper.HitTest(tv, pDragEventArgs.GetPosition(tv)).VisualHit;

            while (k != null)
            {
                if (k is TreeViewItem)
                {
                    TreeViewItem treeNode = k as TreeViewItem;
                    if (treeNode.DataContext is Family)
                    {
                        pItemNode = treeNode;
                    }
                }
                else if (k == tv)
                {
                    Console.WriteLine("Found treeview instance");
                    return;
                }

                k = VisualTreeHelper.GetParent(k);
            }
}

private void trvFamilies_PreviewDragEnter(object sender, DragEventArgs e)
{
            if (!e.Data.GetDataPresent("myFormat") || sender == e.Source)
            {
                e.Effects = DragDropEffects.None;
            }
}

private void trvFamilies_PreviewDrop(object sender, DragEventArgs e)
{
            if (e.Data.GetDataPresent("myFormat"))
            {
                TreeViewItem itemNode;
                FindDropTarget((TreeView)sender, out itemNode, e);
                Family dropItem = (itemNode != null && itemNode.IsVisible ? itemNode.DataContext as Family : null);
                Family dragItem = e.Data.GetData("myFormat") as Family;
                if (dropItem != null)
                {
                    TreeView treeView = sender as TreeView;
                    Console.WriteLine("Index: " + (MyData.IndexOf(dropItem) + 1).ToString());
                    MyData.Remove(dragItem);
                    //MyData.Insert(MyData.IndexOf(dropItem) + 1, dragItem);
                    MyData.Insert(MyData.IndexOf(dropItem) >= 1 ? MyData.IndexOf(dropItem) : 0, dragItem);
                }
                flag = 0;//Release Drag Operation
            }
}

Screenshot(gif):
WpfTVDragDrop

Download Sample: WpfTVDragDrop