Drag and Drop with ListView WPF

I found myself with the task of creating a screen where users could arrange the order of certain items. Now instead of having a crude mechanism of numbering each item accordingly I decided to opt for a more interactive way to order. I chose to show the user a list of items where they could drag and drop each item based on the required order that they wanted to see.  I decided to see what others had done and found a number of implementations on the web but none that gave me what I wanted. I wanted something quick that would allow me to rearrange the items in a list view. The most closest that I came to was Christian Moser drag and drop. Now the problem is that the code here does not show a visual item being dragged, and simply adds the same item to the list creating duplicates. What I have done in this sample is to take Jamie Rodriguez’s article about drag and drop  which shows how to do dragging and use it with Christian Moser sample and provide a ListView WPF application that allows you to rearrange items in a list and visually show the dragging being done with the mouse. I hope this helps someone trying to achieve the same thing.

There are two parts to creating the drag and drop with visual dragging. The first is wiring up the drag and drop events which moves the object when the user drags and drops something. The second is to create an adorner when the drag event occurs and update the adorner as the mouse is moved.

Part One.

First create the ListView object and make sure you set AllowDrop to true, and set the Drop, DragEnter, PreviewMouseMove, and PreviewMouseLeftButtonDown event handlers, as below.

The following is the implementation of the event handlers declared in the xaml extract above.

private void ListViewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
   _startPoint = e.GetPosition(null);
}

 

 private void ListViewPreviewMouseMove(object sender, MouseEventArgs e)
 {
      if (e.LeftButton == MouseButtonState.Pressed)
      {
          Point position = e.GetPosition(null);

          if (Math.Abs(position.X - _startPoint.X) > SystemParameters.MinimumHorizontalDragDistance ||
              Math.Abs(position.Y - _startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
          {
             BeginDrag(e);
          }
      }
 }

These method handlers get the current position of the mouse, and when a left mouse button is pressed it checks to see if the current position yields a listViewItem object then the dragging gets started.

The following code handles the movement of the listViewItem from the drag action. It moves an item in the list view and puts it at the location that you dragged it to.


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

private void ListViewDrop(object sender, DragEventArgs e)
{
   if (e.Data.GetDataPresent("myFormat"))
   {
     string name = e.Data.GetData("myFormat") as string;
     ListViewItem listViewItem = FindAnchestor((DependencyObject)e.OriginalSource);

     if (listViewItem != null)
     {
         string nameToReplace = (string)listView.ItemContainerGenerator.ItemFromContainer(listViewItem);
         int index = listView.Items.IndexOf(nameToReplace);

         if (index >= 0)
         {
            listView.Items.Remove(name);
            listView.Items.Insert(index, name);
         }
     }
     else
     {
        listView.Items.Remove(name);
        listView.Items.Add(name);
     }
   }
}

What brings all this code together is the BeginDrag method which is really the heart of the operation. Here it creates an adorner and handlers to update the adorner so that it shows the item actually being dragged. It also makes the vital call to DragDrop.DoDragDrop which actually fires off the drag/drop operation.

        private void BeginDrag(MouseEventArgs e)
        {
            ListView listView = this.listView;
            ListViewItem listViewItem =
                FindAnchestor((DependencyObject)e.OriginalSource);

            if (listViewItem == null)
                return;

            // get the data for the ListViewItem
            string name = (string)listView.ItemContainerGenerator.ItemFromContainer(listViewItem);

            //setup the drag adorner.
            InitialiseAdorner(listViewItem);

            //add handles to update the adorner.
            listView.PreviewDragOver += ListViewDragOver;
            listView.DragLeave += ListViewDragLeave;
            listView.DragEnter += ListViewDragEnter;

            DataObject data = new DataObject("myFormat", name);
            DragDropEffects de = DragDrop.DoDragDrop(this.listView, data, DragDropEffects.Move);

            //cleanup
            listView.PreviewDragOver -= ListViewDragOver;
            listView.DragLeave -= ListViewDragLeave;
            listView.DragEnter -= ListViewDragEnter;

            if (_adorner != null)
            {
                AdornerLayer.GetAdornerLayer(listView).Remove(_adorner);
                _adorner = null;
            }
        }

Part Two

This part will show how to get the visual drag, I use the visual brush of the listViewItem to get the item to drag and change its opacity so that it has a ghosted appearance. You need to create a DragAdorner object and derive it from the adorner class. Download the sample to see how it is implemented. Before the drag/drop operation is fired off I create the adorner, and add Drag event handlers to ensure the adorner is updated to the mouse location. See below for code extract.

        private void InitialiseAdorner(ListViewItem listViewItem)
        {
            VisualBrush brush = new VisualBrush(listViewItem);
            _adorner = new DragAdorner((UIElement)listViewItem, listViewItem.RenderSize, brush );
            _adorner.Opacity = 0.5;
            _layer = AdornerLayer.GetAdornerLayer(listView as Visual);
            _layer.Add(_adorner);
        }

        private void ListViewQueryContinueDrag(object sender, QueryContinueDragEventArgs e)
        {
            if (this._dragIsOutOfScope)
            {
                e.Action = DragAction.Cancel;
                e.Handled = true;
            }
        }

        private void ListViewDragLeave(object sender, DragEventArgs e)
        {
            if (e.OriginalSource == listView)
            {
                Point point = e.GetPosition(listView);
                Rect rect = VisualTreeHelper.GetContentBounds(listView);

                //Check if within range of list view.
                if (!rect.Contains(point))
                {
                    this._dragIsOutOfScope = true;
                    e.Handled = true;
                }
            }
        }

        void ListViewDragOver(object sender, DragEventArgs args)
        {
            if (_adorner != null)
            {
                _adorner.OffsetLeft = args.GetPosition(listView).X;
                _adorner.OffsetTop = args.GetPosition(listView).Y - _startPoint.Y;
            }
        }

To get a full idea of how the code works download the code and see it in action.
click here to download