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.
WPF MVVM C#: listbox drag and drop without code behind
Check out this CodeProject article. This implementation rely on two attached properties DragEnabled
and DropEnabled
.
<ListBox lib:DragAndDrop.DragEnabled="true"/> // Source
<ListBox lib:DragAndDrop.DropEnabled="true"/> // Target
Drag and Drop ListBoxItems generically
Thanks for the help guys. Turns out to be much simpler then I thought, just had to cast it to an IList since OC implements it and it worked like a charm. Here is the completed code for anyone that might need it. This can be applied to any listbox that is backed by any collection implementing IList including Observable Collections and generic Lists, among others:
void ListBoxItem_Drop(object sender, DragEventArgs e)
{
object Target = ((ListBoxItem)(sender)).DataContext;
object Dropped = e.Data.GetData(Target.GetType());
ListBox container = ((DependencyObject)sender).GetAncestor<ListBox>();
int RemoveIndex = container.Items.IndexOf(Dropped);
int TargetIndex = container.Items.IndexOf(Target);
IList IList = (IList)container.ItemsSource;
if (RemoveIndex < TargetIndex)
{
IList.Insert(TargetIndex + 1, Dropped);
IList.RemoveAt(RemoveIndex);
}
else
if (IList.Count > RemoveIndex)
{
IList.Insert(TargetIndex, Dropped);
IList.RemoveAt(RemoveIndex + 1);
}
}
Ernie
Two Reordering ListBoxes on Same page, prevent user from dragging and dropping between
I figured it out in case anyone else has a similar issue...
I changed the Move function to the following:
private void Move(Item source, int sourceIndex, int targetIndex)
{
IList<Item> prevItems = _items;
try
{
_items.RemoveAt(sourceIndex);
_items.Insert(targetIndex, source);
}
catch(ArgumentOutOfRangeException)
{
//User doesn't need to be notified about this. It just means that they dragged out of the box they were ordering.
//The application does not need to be stopped when this happens.
_items = prevItems;
Debug.WriteLine("User tried to drag between boxes.. Order of boxes were not changed. ");
}
}
I realized that if I remove the item first then I won't have to worry about changing the target or remove index based on the position of the sourceIndex in relation to the targetIndex. Instead I can wrap it in a try catch and look for an ArgumentOutOfRangeException, which will happen if the user tries to drag and drop between. If that happens I reset the box to the previous items.
As far as I can tell this solution works fine... There may have been a simpler way to go about this.
How to replace text using drag and drop from listbox to a textbox in WPF?
Try to handle the PreviewDrop event instead of Drop
:
private void textbox1_PreviewDrop(object sender, DragEventArgs e)
{
e.Handled = true;
string tstring;
tstring = e.Data.GetData(DataFormats.StringFormat).ToString();
textbox1.Text = tstring;
}
WPF drag-drop rearrangeable wrappanel items
It took some time, but I was able to figure it out after Micky's suggestion. I'm posting an answer here for future reference and if anyone else is looking for this mechanic. The following code snippets should work by just pasting them into their appropriate files.
Here's what worked:
Make the ListBox arrange items like a WrapPanel via a custom/default style (mine is called Default.xaml).
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<!--'WrapPanel' can be replaced with other controls if you want it to display differently-->
<WrapPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<!--Controls in each item-->
<local:DocPage />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Set up the ListBox control (i.e. MainWindow.xaml):
<ListBox x:Name="lbx_Pages" AllowDrop="True" DragEnter="lbx_Pages_DragEnter" PreviewMouseLeftButtonDown="lbx_Pages_PreviewMouseLeftButtonDown" PreviewMouseMove="lbx_Pages_PreviewMouseMove" Drop="lbx_Pages_PagesDrop"/>
Impliment the controls in the .cs file (i.e. MainWindow.xaml.cs):
private Point dragStartPoint;
// Bindable list of pages (binding logic omitted-out of scope of this post)
private static ObservableCollection<DocPage> pages = new ObservableCollection<DocPage>();
// Find parent of 'child' of type 'T'
public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
do
{
if (child is T)
return (T)child;
child = VisualTreeHelper.GetParent(child);
} while (child != null);
return null;
}
private void lbx_Pages_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
dragStartPoint = e.GetPosition(null);
}
private void lbx_Pages_PreviewMouseMove(object sender, MouseEventArgs e)
{
ListBoxItem item = null;
DataObject dragData;
ListBox listBox;
DocPage page;
// Is LMB down and did the mouse move far enough to register a drag?
if (e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(dragStartPoint.X - e.GetPosition(null).X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(dragStartPoint.Y - e.GetPosition(null).Y) > SystemParameters.MinimumVerticalDragDistance))
{
// Get the ListBoxItem object from the object being dragged
item = FindParent<ListBoxItem>((DependencyObject)e.OriginalSource);
if (null != item)
{
listBox = sender as ListBox;
page = (DocPage)listBox.ItemContainerGenerator.ItemFromContainer(item);
dragData = new DataObject("pages", page);
DragDrop.DoDragDrop(item, dragData, DragDropEffects.Move);
}
}
}
private void lbx_Pages_PagesDrop(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent("pages"))
return;
DocPage draggedItem = e.Data.GetData("pages") as DocPage;
// Hit-test needed for rearranging items in the same ListBox
HitTestResult hit = VisualTreeHelper.HitTest((ListBox)sender, e.GetPosition((ListBox)sender));
DocPage target = (DocPage)FindParent<ListBoxItem>(hit.VisualHit).DataContext;
int removeIdx = lbx_Pages.Items.IndexOf(draggedItem);
int targetIdx = lbx_Pages.Items.IndexOf(target);
if(removeIdx < targetIdx)
{
pages.Insert(targetIdx + 1, draggedItem);
pages.RemoveAt(removeIdx);
}
else
{
removeIdx++;
if(pages.Count+1 > removeIdx)
{
pages.Insert(targetIdx, draggedItem);
pages.RemoveAt(removeIdx);
}
}
}
private void lbx_Pages_DragEnter(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent("pages") || sender == e.Source)
e.Effects = DragDropEffects.None;
}
Related Topics
How to Parse Very Huge Xml Files in C#
How to Ignore the Utf-8 Byte Order Marker in String Comparisons
Xdocument.Validate Is Always Successful
Selenium Stops When Browser Is Manually Interrupted
Programmatic Control of Virtual Desktops in Windows 10
How to Define a Method in Razor
How to Print Webview Content in a Windows Store App
Measure a String Without Using a Graphics Object
How to Save Picturebox.Image to File
Minimal and Correct Way to Map One-To-Many with Nhibernate
Declaring a New Instance of a Class in C#
How to Convert Securestring to System.String
Open File Dialog and Select a File Using Wpf Controls and C#