punker76 / gong-wpf-dragdrop

The GongSolutions.WPF.DragDrop library is a drag'n'drop framework for WPF
BSD 3-Clause "New" or "Revised" License
2.25k stars 392 forks source link

Problem with DragDrop.DefaultDropHandler.Drop() when dropping multiple items across groups #435

Closed RickStrahl closed 1 year ago

RickStrahl commented 2 years ago

I'm trying to do custom handling for multiple items in a grouped list box by implementing an IDropHandler. The default behavior without the drop handler works fine all around for single and multi-item drops. However, using IDropHandler fails when calling DragDrop.DefaultDropHandler.Drop() when multiple items are selected and dragged across groups.

Here's my implementation:

This code works for:

It fails for:

Here's what this implementation looks like at the failure point:

image

The error is:

image

Here's my code:

public class SessionRequestListModel : IDropTarget 
{
    ...
        public void DragOver(IDropInfo dropInfo)
        {
            if (dropInfo.KeyStates.HasFlag( DragDropKeyStates.ControlKey))
                dropInfo.Effects = DragDropEffects.Copy;
            else
                dropInfo.Effects = DragDropEffects.Move;

            dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
        }

        public void Drop(IDropInfo dropInfo)
        {
            var item = dropInfo.TargetItem as HttpRequestData;
            if (item == null) return;
            var sourceItems = dropInfo.Data as List<object>;
            if (sourceItems == null)
            {
                var sourceItem = dropInfo.Data as HttpRequestData;
                if (sourceItem == null) return;
                sourceItems = new List<object>();
                sourceItems.Add(sourceItem);

            }

            var resultItems = new List<object>();
            foreach (HttpRequestData source in sourceItems)
            {
                HttpRequestData copied = source;
                if (dropInfo.Effects == DragDropEffects.Copy)
                {
                    copied = HttpRequestData.Copy(source);
                    if (!string.IsNullOrEmpty(copied.Name))
                        copied.Name += " (copy)";
                    else
                        copied.Url += " (copy)";
                }

                if (copied.Group != item.Group)
                {
                    copied.Group = item.Group;
                }

                resultItems.Add(copied);
            }

            dropInfo.Data = resultItems;

            // TODO: this blows up with multiple items across groups
            // but works for single items or multiple items within same group
            GongSolutions.Wpf.DragDrop.DragDrop.DefaultDropHandler.Drop(dropInfo);           

            GroupRefresh();
        }
}

Basically what the code is doing is creating a new Data item with a List of drop items which may have updated group data which is a property on the item model. The group is explicitly updated in GroupRefresh(), but the code never gets there for the multi-drop operation. It does work however for a single item which is strange.

I'm creating new data items in order to properly support the Copy operation which needs to create new (not the same reference) items which makes this code more verbose than it otherwise would be. I suspect it has something to do with the new collection that's causing the problems, but still odd that it works with a single item even though that is also in the collection.

RickStrahl commented 2 years ago

I haven't tried your Clonable solution yet, but I don't think that's the problem. The items in the example above aren't copied/cloned because it's a move operation so just using the original objects and re-assigning to the new collection of items. The odd thing is that this doesn't fail with the exact same operation in the same group. Also copying and moving multiple items does work as long as it's in the same group.

I presume the IDragItemClonable interface can work to handle the copy operation more cleanly - that is a nice and more obvious improvement, but don't think applies to this problem here.

To check out what's happening, I pulled down the Gong code to see what is actually failing.

When running in my app, here's the actual error location and data at point of failure in DefaultDropHandler.cs:

image

What's failing is the ObservableCollection.Move() operation despite the items at indexes existing.

I'm not sure why this is failing. It's doing a move on the Observable collection so these are the original items (not copied/cloned), there are items at these indexes. Not sure why this would fail. The first iteration in the selected items loop works - it's the second selected item that fails.

RickStrahl commented 2 years ago

So I tried out your IDragItemClonable interface but couldn't get that to work - where do I specify that the interface is used or is that automatic? I have only this and don't see another place to provide a reference to the interface on the control like the DropHandler:

public class SessionRequestListModel : IDropTarget, IDragItemCloneable

My full code (old code w/o) is here (private WebSurge repo but you have access):

https://github.com/RickStrahl/WebSurge2/blob/8bd81f87d5c502848bb73c493fc8c701e42b0a67/WebSurge/Controls/SessionRequestList.xaml.cs#L181

RickStrahl commented 2 years ago

Aaargh...

So although the code blows up on the line above it looks like it actually works to move items.

For now I added the following to my code:


            // TODO: this blows up with multiple items across groups
            // but works for single items or multiple items within same group
            try
            {
                GongSolutions.Wpf.DragDrop.DragDrop.DefaultDropHandler.Drop(dropInfo);
            }
            catch
            {
                AppModel.Current.Window.Statusbar.ShowStatusError(
                    "Can't drag multiple items between groups. Please only drag single items between groups.");
            }

and now the items drop. However, the exception occurs (I see the error message on the status bar), but the items are moved/copied.

draganddrop

In the end the code that works looks like this:

public void DragOver(IDropInfo dropInfo)
{
    dropInfo.Effects = (dropInfo.KeyStates.HasFlag( DragDropKeyStates.ControlKey)) ?
        dropInfo.Effects = DragDropEffects.Copy :
        dropInfo.Effects = DragDropEffects.Move;

    dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
}

public void Drop(IDropInfo dropInfo)
{
    var item = dropInfo.TargetItem as HttpRequestData;
    if (item == null) return;
    var sourceItems = dropInfo.Data as List<object>;
    if (sourceItems == null)
    {
        var sourceItem = dropInfo.Data as HttpRequestData;
        if (sourceItem == null) return;
        sourceItems = new List<object>();
        sourceItems.Add(sourceItem);
    }

    var resultItems = new List<object>();

    for (var index = 0; index < sourceItems.Count; index++)
    {
        var source = (HttpRequestData) sourceItems[index];
        HttpRequestData copied = source;

        if (dropInfo.Effects == DragDropEffects.Copy)
        {
            copied = HttpRequestData.Copy(source);
            if (!string.IsNullOrEmpty(copied.Name))
                copied.Name += " (copy)";
            else
                copied.Url += " (copy)";
        }

        if (copied.Group != item.Group)
        {
            copied.Group = item.Group;
        }

        resultItems.Add(copied);
    }

    dropInfo.Data = resultItems;

    // TODO: this blows up with multiple items across groups
    // but works for single items or multiple items within same group
    try
    {
        GongSolutions.Wpf.DragDrop.DragDrop.DefaultDropHandler.Drop(dropInfo);
    }
    catch
    {
        // TODO: Exception fires here, but the default move/copy operation appears to work regardless
    }

    GroupRefresh();
}