WPF C#: Rearrange items in listbox via drag and drop

I've tried creating one using ObservableCollection. Have a look.

    ObservableCollection<Emp> _empList = new ObservableCollection<Emp>();

    public Window1()
    {
        InitializeComponent();

        _empList .Add(new Emp("1", 22));
        _empList .Add(new Emp("2", 18));
        _empList .Add(new Emp("3", 29));
        _empList .Add(new Emp("4", 9));
        _empList .Add(new Emp("5", 29));
        _empList .Add(new Emp("6", 9));
        listbox1.DisplayMemberPath = "Name";
        listbox1.ItemsSource = _empList;
        
        Style itemContainerStyle = new Style(typeof(ListBoxItem));
        itemContainerStyle.Setters.Add(new Setter(ListBoxItem.AllowDropProperty, true));
        itemContainerStyle.Setters.Add(new EventSetter(ListBoxItem.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(s_PreviewMouseLeftButtonDown)));
        itemContainerStyle.Setters.Add(new EventSetter(ListBoxItem.DropEvent, new DragEventHandler(listbox1_Drop)));
        listbox1.ItemContainerStyle = itemContainerStyle;
    }

Drag and drop process:

    void s_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {

        if (sender is ListBoxItem)
        {
            ListBoxItem draggedItem = sender as ListBoxItem;
            DragDrop.DoDragDrop(draggedItem, draggedItem.DataContext, DragDropEffects.Move);
            draggedItem.IsSelected = true;
        }
    }

    void listbox1_Drop(object sender, DragEventArgs e)
    {
        Emp droppedData = e.Data.GetData(typeof(Emp)) as Emp;
        Emp target = ((ListBoxItem)(sender)).DataContext as Emp;

        int removedIdx = listbox1.Items.IndexOf(droppedData);
        int targetIdx = listbox1.Items.IndexOf(target);

        if (removedIdx < targetIdx)
        {
            _empList.Insert(targetIdx + 1, droppedData);
            _empList.RemoveAt(removedIdx);
        }
        else
        {
            int remIdx = removedIdx+1;
            if (_empList.Count + 1 > remIdx)
            {
                _empList.Insert(targetIdx, droppedData);
                _empList.RemoveAt(remIdx);
            }
        }
    }

Note:

  • One thing that sucks in this implementation is that since it uses the PreviewMouseLeftButtonDown event, the dragged item does not look like a selected item.
  • And also for an easier implementation, the drop target is the list box items and not the listbox itself - might need a better solution for this.

Using dnr3's answers I have created version with fixed selection issues.

Window1.xaml

<Window x:Class="ListBoxReorderDemo.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ListBoxReorderDemo" Height="300" Width="300"
        WindowStartupLocation="CenterScreen">
    <Grid>
        <ListBox x:Name="listBox"/>
    </Grid>
</Window>

Window1.xaml.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace ListBoxReorderDemo
{
    public class Item
    {
        public string Name { get; set; }
        public Item(string name)
        {
            this.Name = name;
        }
    }

    public partial class Window1 : Window
    {
        private Point _dragStartPoint;

        private T FindVisualParent<T>(DependencyObject child)
            where T : DependencyObject
        {
            var parentObject = VisualTreeHelper.GetParent(child);
            if (parentObject == null)
                return null;
            T parent = parentObject as T;
            if (parent != null)
                return parent;
            return FindVisualParent<T>(parentObject);
        }

        private IList<Item> _items = new ObservableCollection<Item>();

        public Window1()
        {
            InitializeComponent();

            _items.Add(new Item("1"));
            _items.Add(new Item("2"));
            _items.Add(new Item("3"));
            _items.Add(new Item("4"));
            _items.Add(new Item("5"));
            _items.Add(new Item("6"));

            listBox.DisplayMemberPath = "Name";
            listBox.ItemsSource = _items;

            listBox.PreviewMouseMove += ListBox_PreviewMouseMove;

            var style = new Style(typeof(ListBoxItem));
            style.Setters.Add(new Setter(ListBoxItem.AllowDropProperty, true));
            style.Setters.Add(
                new EventSetter(
                    ListBoxItem.PreviewMouseLeftButtonDownEvent,
                    new MouseButtonEventHandler(ListBoxItem_PreviewMouseLeftButtonDown)));
            style.Setters.Add(
                    new EventSetter(
                        ListBoxItem.DropEvent, 
                        new DragEventHandler(ListBoxItem_Drop)));
            listBox.ItemContainerStyle = style;
        }

        private void ListBox_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            Point point = e.GetPosition(null);
            Vector diff = _dragStartPoint - point;
            if (e.LeftButton == MouseButtonState.Pressed &&
                (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
            {
                var lb = sender as ListBox;
                var lbi = FindVisualParent<ListBoxItem>(((DependencyObject)e.OriginalSource));
                if (lbi != null)
                {
                    DragDrop.DoDragDrop(lbi, lbi.DataContext, DragDropEffects.Move);
                }
            }
        }
        private void ListBoxItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _dragStartPoint = e.GetPosition(null);
        }

        private void ListBoxItem_Drop(object sender, DragEventArgs e)
        {
            if (sender is ListBoxItem)
            {
                var source = e.Data.GetData(typeof(Item)) as Item;
                var target = ((ListBoxItem)(sender)).DataContext as Item;

                int sourceIndex = listBox.Items.IndexOf(source);
                int targetIndex = listBox.Items.IndexOf(target);

                Move(source, sourceIndex, targetIndex);
            }
        }

        private void Move(Item source, int sourceIndex, int targetIndex)
        {
            if (sourceIndex < targetIndex)
            {
                _items.Insert(targetIndex + 1, source);
                _items.RemoveAt(sourceIndex);
            }
            else
            {
                int removeIndex = sourceIndex + 1;
                if (_items.Count + 1 > removeIndex)
                {
                    _items.Insert(targetIndex, source);
                    _items.RemoveAt(removeIndex);
                }
            }
        }
    }
}

Version with support for generics and data binding.

Window1.xaml

<Window x:Class="ListBoxReorderDemo.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ListBoxReorderDemo"
        Title="ListBoxReorderDemo" Height="300" Width="300"
        WindowStartupLocation="CenterScreen">
    <Grid>
        <local:ItemDragAndDropListBox x:Name="listBox" ItemsSource="{Binding}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </local:ItemDragAndDropListBox>
    </Grid>
</Window>

Window1.xaml.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace ListBoxReorderDemo
{
    public class DragAndDropListBox<T> : ListBox 
        where T : class
    {
        private Point _dragStartPoint;

        private P FindVisualParent<P>(DependencyObject child) 
            where P : DependencyObject
        {
            var parentObject = VisualTreeHelper.GetParent(child);
            if (parentObject == null)
                return null;

            P parent = parentObject as P;
            if (parent != null)
                return parent;

            return FindVisualParent<P>(parentObject);
        }

        public DragAndDropListBox()
        {
            this.PreviewMouseMove += ListBox_PreviewMouseMove;

            var style = new Style(typeof(ListBoxItem));

            style.Setters.Add(new Setter(ListBoxItem.AllowDropProperty, true));

            style.Setters.Add(
                new EventSetter(
                    ListBoxItem.PreviewMouseLeftButtonDownEvent,
                    new MouseButtonEventHandler(ListBoxItem_PreviewMouseLeftButtonDown)));

            style.Setters.Add(
                    new EventSetter(
                        ListBoxItem.DropEvent, 
                        new DragEventHandler(ListBoxItem_Drop)));

            this.ItemContainerStyle = style;
        }

        private void ListBox_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            Point point = e.GetPosition(null);
            Vector diff = _dragStartPoint - point;
            if (e.LeftButton == MouseButtonState.Pressed &&
                (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
            {
                var lb = sender as ListBox;
                var lbi = FindVisualParent<ListBoxItem>(((DependencyObject)e.OriginalSource));
                if (lbi != null)
                {
                    DragDrop.DoDragDrop(lbi, lbi.DataContext, DragDropEffects.Move);
                }
            }
        }

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

        private void ListBoxItem_Drop(object sender, DragEventArgs e)
        {
            if (sender is ListBoxItem)
            {
                var source = e.Data.GetData(typeof(T)) as T;
                var target = ((ListBoxItem)(sender)).DataContext as T;

                int sourceIndex = this.Items.IndexOf(source);
                int targetIndex = this.Items.IndexOf(target);

                Move(source, sourceIndex, targetIndex);
            }
        }

        private void Move(T source, int sourceIndex, int targetIndex)
        {
            if (sourceIndex < targetIndex)
            {
                var items = this.DataContext as IList<T>;
                if (items != null)
                {
                    items.Insert(targetIndex + 1, source);
                    items.RemoveAt(sourceIndex);
                }
            }
            else
            {
                var items = this.DataContext as IList<T>;
                if (items != null)
                {
                    int removeIndex = sourceIndex + 1;
                    if (items.Count + 1 > removeIndex)
                    {
                        items.Insert(targetIndex, source);
                        items.RemoveAt(removeIndex);
                    }
                }
            }
        }
    }

    public class Item
    {
        public string Name { get; set; }
        public Item(string name)
        {
            this.Name = name;
        }
    }

    public class ItemDragAndDropListBox : DragAndDropListBox<Item> { }

    public partial class Window1 : Window
    {
        private IList<Item> _items = new ObservableCollection<Item>();

        public Window1()
        {
            InitializeComponent();

            _items.Add(new Item("1"));
            _items.Add(new Item("2"));
            _items.Add(new Item("3"));
            _items.Add(new Item("4"));
            _items.Add(new Item("5"));
            _items.Add(new Item("6"));

            listBox.DataContext = _items;
        }
    }
}