Drag and Drop ListBoxItems generically

2019-06-09 01:35发布

问题:

I want to implement a drag and drop on multiple listboxes that are data bound - using the MVVM pattern. I am not trying to drag and drop between listboxes but rather want the user to be able to drag/drop listboxitems in each listbox so they can rearrange the sort order. I found this post on SO which was VERY helpful:

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

I wanted to try and make the methods more "generic" so that it would work on any listbox that is binding to different types of Observable Collections. So say this is my XAML in the VIEW:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <Style TargetType="{x:Type ListBoxItem}" x:Key="ListBoxItemDragDrop">
            <Setter Property="AllowDrop" Value="True" />
            <EventSetter Event="PreviewMouseMove" Handler="ListBoxItem_PreviewMouseMoveEvent" />
            <EventSetter Event="Drop" Handler="listbox1_Drop" />
        </Style>
    </Window.Resources>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <ListBox Name="listbox1"
                 ItemsSource="{Binding OCofType1}"
                 ItemContainerStyle="{StaticResource ListBoxItemDragDrop}" />

        <ListBox Name="listbox2" Grid.Column="1"
                 ItemsSource="{Binding OCofType2}"
                 ItemContainerStyle="{StaticResource ListBoxItemDragDrop}"/>
    </Grid>

</Window>

Where the OC bindings are ObservalbeCollection<Type1> and ObservalbeCollection<Type2>. Here is the method in the VIEW that grabs the mouse move event and checks if it is a drag:

void ListBoxItem_PreviewMouseMoveEvent(object sender, MouseEventArgs e)
{
    if (e.LeftButton == MouseButtonState.Pressed && sender is ListBoxItem)
    {
        ListBoxItem draggedItem = (ListBoxItem)sender;
        DragDrop.DoDragDrop(draggedItem, draggedItem.DataContext, DragDropEffects.Move);
        draggedItem.IsSelected = true;
    }
}

This seems "generic" enough. Next would be the method, also in the VIEW, the handles the drop and this is where I get stuck:

void ListBoxItem_Drop(object sender, DragEventArgs e)
{
    object Target = ((ListBoxItem)(sender)).DataContext;
    object Dropped = e.Data.GetData(Target.GetType());

    int RemoveIndex = listbox1.Items.IndexOf(Dropped);
    int TargetIndex = listbox1.Items.IndexOf(Target);

    ListBox container = ((DependencyObject)sender).GetAncestor<ListBox>();

    if (RemoveIndex < TargetIndex)
    {
        //THESE WILL NOT WORK IF I AM DOING BINDINGS THROUGH THE ITEMSSOURCE
        //container.Items.Insert(RemoveIndex + 1, Dropped);
        //container.Items.RemoveAt(RemoveIndex);

        //SO HAVE TO USE THE ITEMSSOURCE DIRECTLY BUT HOW WITHOUT A SPECIFIC CAST TO THE OC<TYPE#>
        container.ItemsSource.Insert(RemoveIndex + 1, Dropped);  //ERROR: IENUMERATOR DOES NOT CONTAIN A DEFINITION FOR INSERT....
        container.ItemsSource.RemoveAt(RemoveIndex); //ERROR: IENUMERATOR DOES NOT CONTAIN A DEFINITION FOR REMOVEAT....
    }
    else
        if (container.Items.Count > RemoveIndex)
        {
            //THESE WILL NOT WORK IF I AM DOING BINDINGS THROUGH THE ITEMSSOURCE                    
            //container.Items.Insert(TargetIndex, Dropped);
            //container.Items.RemoveAt(RemoveIndex + 1); 

            //SO HAVE TO USE THE ITEMSSOURCE DIRECTLY BUT HOW WITHOUT A SPECIFIC CAST TO THE OC<TYPE#>
            container.ItemsSource.Insert(TargetIndex, Dropped);  //ERROR: IENUMERATOR DOES NOT CONTAIN A DEFINITION FOR INSERT....
            container.ItemsSource.RemoveAt(RemoveIndex + 1);  //ERROR: IENUMERATOR DOES NOT CONTAIN A DEFINITION FOR REMOVEAT....

        }
}

That find ancestor functions is this (from another post on SO):

static T FindAnchestor<T>(DependencyObject current) where T : DependencyObject
{
    do
    {
        if (current is T)
            return (T)current;

        current = VisualTreeHelper.GetParent(current);
    }
    while (current != null);
    return null;
}

In the drop function, this might work if I was directly adding to the ListBox's List collections. But since I am doing bindings to collections in the VIEWMODEL, it will error saying that I have to work with those objects though the ItemsSource. But if I use the ItemsSource I would have to make mutible versions of the function for each OC Type since I would not what to cast the ItemsSource too at run time. I could keep it down to 1 function using an if statement which determine what to cast it to explicitly but it would be alot cleaner not having to remember to update that if for each new OC it is applied to.

The question is then how do I add/move items to the ItemsSource without knowing exactly what to cast too?

Thanks for any help.

回答1:

This is a VERY handy tool/framework. Gong WPF Drag & Drop



回答2:

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