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;
}
}
}