2

I'm trying to write some generic code for handling drops in WPF drop targets. AllowDrop is set to true, and I've hooked onto DragEnter, DragOver, DragLeave, & Drop on the drop target UIElement. Using the bubbling events enables nesting of drop targets.

Note: I have no access to the drag source - this is inter-application drag & drop.

If I have some UI cleanup to perform at the end of a potential drop and the user presses Esc to cancel the drop, the drop target never seems to gets a specific event that I can differentiate from all the others. Drop is easy, but what indicates a cancel?

The problem I have is this:

  • DragLeave is a bubbling routed event.
  • e.OriginalSource is always set for this event (and the corresponding Preview) via hittesting.
  • The target is an ItemsControl (Listbox is what I've currently been testing with).

As I drag over my intended drop target, I get loads of DragLeave events from the child visuals within the target. I never get any from the target itself. Grids, rectangles, borders, text blocks, they all happily send me DragLeave, but none from the actual ItemsControl I'm connected up to. I thought it might be a hit-testing problem, but I've set the Background of the ItemsControl to a colour, and it makes no difference.

What am I missing? How am I supposed to determine that a drop operation has definitely finished?

(The actual problem I'm trying to solve is that I'm implementing some custom dragging behaviour in a TreeView that expands folders when you hover over them, and cancels timers & undoes the expansion when the drop is finished, and more to come, but I can't even get the events to fire sensibly for a ListBox).

Marvin
  • 229
  • 1
  • 3
  • 11
naviwhack
  • 108
  • 1
  • 8
  • There's a related question at http://stackoverflow.com/questions/5447301/wpf-drag-drop-when-does-dragleave-fire; I am hoping that there's a better solution though. – naviwhack Oct 28 '11 at 15:37

2 Answers2

0

check for e.Source and not e.OriginalSource, if you have set DropTarget="True" on the ItemsControl & you have DropOver event attached to the ItemsControl, the event argument e.Source should be ItemsControl.

Shrinand
  • 351
  • 3
  • 5
  • 1
    This does not answer the question. The question is about detecting that a drag-drop operation was cancelled in the drop TARGET. Your answer is about how to initiate a cancellation in the drop SOURCE. The QueryContinueDrag event is never raised in the drop target. – Dana Cartwright Jan 29 '13 at 16:10
0

You do have a complex scenario here so this will start basic in hopes of giving you a direction and hopefully a solution.

The framework will only inform of a DragEnter event if the control is marked with AllowDrop = true. So make sure you've done that. It sounds like you have but I just want to be sure.

I'm not sure why you need the DragLeave event but if it's to grab the selected data the easiest way to get the data is not to hook into DragLeave but to hook into PreviewMouseMove. You can then determine if the mouse is pressed and how far of a distance to move before enacting a DoDragDrop.

In this event, you also have the ability to add and analyze Drag Data. When the drag starts you can create a new DataObject and send it with the DoDragDrop call:

  private static void listBox_PreviewMouseMove(object sender, MouseEventArgs e)
  {
     // Get the current mouse position
     var mousePos = e.GetPosition(null);
     var diff = startPoint - mousePos;

     if (canScroll && e.LeftButton == MouseButtonState.Pressed &&
         (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
          Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
     {
        var dataObject = (from lbi in startList.Items.Cast<object>().Select((t, i) => (ListBoxItem)startList.ItemContainerGenerator.ContainerFromIndex(i)) where lbi != null && lbi.IsSelected select Convert.ToString(lbi.Content)).ToList();

        // Initialize the drag & drop operation
        var listBoxData = new ListBoxData(){ StartList = startList, Data = dataObject};
        var dragData = new DataObject("listBoxData", listBoxData);

        System.Windows.DragDrop.DoDragDrop(startList, dragData, DragDropEffects.Move);
     }
  }

This drag data is now accessible through the DragEventArgs object by using (if you don't rename the event parameter, it's e):

e.Data.GetData("listBoxData")

My suggestion is to add some unique information to the drag event data to differentiate the event based on the data. Either your data item from the listbox selection, or a new class holding your data item and another indicator if needed.

Josh
  • 2,955
  • 1
  • 19
  • 28
  • Unfortunately this assumes I'm the one writing the DoDragDrop part. I am only able to affect the code that is receiving the drop. If I could put code where DoDragDrop is called, I'd know the result of the drop from the return value (e.g. user presses Esc, it'd be None), but unfortunately, that bit of code is in the other application :) – naviwhack Oct 28 '11 at 15:27
  • In your DragOver event handler have you attempted to see if it has any data? – Josh Oct 28 '11 at 15:32
  • Yes - the DragOver event examines the incoming format and sets Effects accordingly. This is during a case where Effects has been set to other than None because it's a desired drop i.e. letting go of the mouse button would yield a Drop event. If a drop occurs, I can clean up my UI from the Drop event; the Esc cancel is the bit that is perplexing me. – naviwhack Oct 28 '11 at 15:40
  • 1
    Unfortunately, there is nothing raised if a drag is canceled. The only thing I can think of is to track a boolean in all your drag over events, then handle PreviewKeyDown on your window or control to see if ESC is pressed while that boolean is true. – Josh Oct 28 '11 at 17:25
  • @Josh: But, if it is "inter-application drag & drop", the application won't be focused, i.e. will not respond to keyboard events, see my question: https://stackoverflow.com/questions/67324983/how-can-i-detect-if-a-file-drag-operation-was-canceled-via-esc – mike Apr 29 '21 at 21:02