Wpf C#: Rearrange Items in Listbox via Drag and Drop

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



Leave a reply



Submit