Drag & Drop with attached properties – Part 2

In my previous post on this topic, I gave a quick overview of how Drag and Drop can be accomplished using attached properties. To review, here are the key elements of the implementation:

  • DragDropManager - top level class that exposes the attached properties (DragSourceAdvisor, DropTargetAdvisor); does all the DnD plumbing and fires methods on the IDragSourceAdvisor and IDropTargetAdvisor interfaces
  • IDragSourceAdvisor - implemented by the source control and advises the DragDropManager on how the source behaves during DnD
  • IDropTargetAdvisor - implemented by the target control and advises the DragDropManager on how the target behaves during DnD

The property changed handlers for the attached properties hooks up events for the source and target controls. Below is the handler for the DragSourceAdvisor property. Note that here I am hooking up to the Mouse events for initiating DragDrop. These events are primarily used to detect the drag gesture. For more information read Marcelo’s blog.

private static void OnDragSourceAdvisorChanged(DependencyObject depObj,
DependencyPropertyChangedEventArgs args)
{
    UIElement sourceElt = depObj as UIElement;
    if (args.NewValue != null && args.OldValue == null)
    {
        sourceElt.PreviewMouseLeftButtonDown +=
new MouseButtonEventHandler(DragSource_PreviewMouseLeftButtonDown);
        sourceElt.PreviewMouseMove +=
new MouseEventHandler(DragSource_PreviewMouseMove);
        sourceElt.PreviewMouseLeftButtonUp +=
new MouseButtonEventHandler(DragSource_PreviewMouseLeftButtonUp);
        sourceElt.PreviewGiveFeedback +=
new GiveFeedbackEventHandler(DragSource_PreviewGiveFeedback);

        // Set the Drag source UI
        IDragSourceAdvisor advisor = args.NewValue as IDragSourceAdvisor;
        advisor.SourceUI = sourceElt;
    }
    else if (args.NewValue == null && args.OldValue != null)
    {
        sourceElt.PreviewMouseLeftButtonDown -= DragSource_PreviewMouseLeftButtonDown;
        sourceElt.PreviewMouseMove -= DragSource_PreviewMouseMove;
        sourceElt.PreviewMouseLeftButtonUp -= DragSource_PreviewMouseLeftButtonUp;
        sourceElt.PreviewGiveFeedback -= DragSource_PreviewGiveFeedback;
    }
}

and the DropTargetAdvisor property handler:

private static void OnDropTargetAdvisorChanged(DependencyObject depObj,
DependencyPropertyChangedEventArgs args)
{
    UIElement targetElt = depObj as UIElement;
    if (args.NewValue != null && args.OldValue == null)
    {
        targetElt.PreviewDragEnter += new DragEventHandler(DropTarget_PreviewDragEnter);
        targetElt.PreviewDragOver += new DragEventHandler(DropTarget_PreviewDragOver);
        targetElt.PreviewDragLeave += new DragEventHandler(DropTarget_PreviewDragLeave);
        targetElt.PreviewDrop += new DragEventHandler(DropTarget_PreviewDrop);
        targetElt.AllowDrop = true;

        // Set the Drag source UI
        IDropTargetAdvisor advisor = args.NewValue as IDropTargetAdvisor;
        advisor.TargetUI = targetElt;
    }
    else if (args.NewValue == null && args.OldValue != null)
    {
        targetElt.PreviewDragEnter -= DropTarget_PreviewDragEnter;
        targetElt.PreviewDragOver -= DropTarget_PreviewDragOver;
        targetElt.PreviewDragLeave -= DropTarget_PreviewDragLeave;
        targetElt.PreviewDrop -= DropTarget_PreviewDrop;
        targetElt.AllowDrop = false;
    }
}

Since all the event handlers are part of the DragDropManager, all the dirty work ;) will be done here and the appropriate interface methods will be called. The calls to these interface methods are spread across the different event handlers.

Examples

In the attached zip, I have included two examples:

  • Three panels where you can drag and drop buttons between these panels
  • DragCanvas example where the source and target is the same. Apparently while writing this post, I realized that the DragCanvas example is similar to one that Josh Smith posted sometime back. However my example has far less code since all the DnD logic is hidden away ;)  

The source for both the examples should be pretty self-explanatory so I am not going over it. Since both the examples are part of the same project, you will have to change the StartupUri in App.xaml to switch between examples. Use the following StartupUris:

CanvasExample: StartupUri=”CanvasExample/DragCanvasExample.xaml”
PanelExample: StartupUri=”PanelExample/Window1.xaml”

Anyways, feel free to use the code in your own apps. If you do come up with a scenario where the interface methods are not sufficient, say, drop me a line and I would be glad to fix it. Any feedback…good, bad, ugly is welcome ;)

Download Zip

Technorati tags: , , ,

Similar Posts:

26 responses to “Drag & Drop with attached properties – Part 2”

  1. Pavan Podila

    By Model-object I meant the instance of your Data model. If you can define a DataTemplate for it, you could create a ContentPresenter, set the Content to your data-model and the ContentTemplate to your DataTemplate. Then using the VisualBrush of the ContentPresenter you could render the graphics for the DnD hover. HTH.

  2. (no name)
    I’ve tried passing an object (UIElement) instead of a XML string, which didn’t work. Thanks again for your suggestions. 
  3. Pavan Podila

    Yes, I am aware of the problem with the use of x:Name in UserControls. I think its an issue with the XamlReader class and more specifically the Xaml Parser. On a similar note I have also encountered issues using x:Name for elements inside a ResourceDictionary.
    The other approach I can suggest is to pass on Model objects via DnD. Since I already provide you the visual brush on the Source side you should be able to see the actual element being moved. When hovering over the Target, instead of using the Serialized Xaml, you could recreate the visual using Model objects + DataTemplates. Let me know if that sounds feasible for your scenario.

    Thanks!

  4. (no name)
    Very very nice & helpful work!!! I’ve successfully used the code for DnDing user controls across different WrapPanels.
    However, the user control cannot have any x:Name in its XAML file. The control has to be built up in code. For example, I have a user control with 2 Textbox controls. It produces an error (duplicate names in DataObject GetData) when dragging it to another WrapPanel container if names were assigned to the Textboxes in XAML. Have you encountered this kind of issues? 
    Could you please also provide suggestions on how to bind a DnD Advisor to a Container from C# code?  Thanks!
  5. Pavan Podila

    Hi Riccardo,
         I am currently in the process of tweaking my solution (with the feedback that I’ve received from the comments). I’ll put up the new source soon.

    As regards your case, having the advisors for the Listboxes is the right approach. Having the advisor for the Window itself seems complicated, as you may have to do lots of checking to see if you are dragging the right element.

  6. Riccardo
    Hello, very nice solution, actually it is what I was going to write myself! :-)
    Anyway I’ve just a question:
    I have an application with a couple of listboxes. I’d like to DnD elements from one listbox to another.
    With your approach I need to implement a couple of Advisors, one for the ListBoxes (or their content) and one for the background of my window, because I want to see the animation of the element being dragged on all the surface of my window. Anyway I’d like to have parts of my window where it is not permitted to Drop stuff, but I can still see the Adorner moving. I think that I should change the shape of the cursor, but, even following the hints in your code I’ve not been able to do that.
    Any hint?
    Thanks
  7. Pavan Podila

    Yes, I was planning to add that for v2. The only thing is that you won’t get a preview feedback for cross-app DnD, but will be using the OS-specific version of the DnD cursor.

    The oven has been turned on for baking ;)

  8. (no name)

    hey pavan

    it is meant for cross app and thats why i need the draggedelt to be updated. could you pls update the library.

  9. Pavan Podila

    Quite possible ;) Yes, I don’t support cross-application support, not yet.

  10. (no name)
    i think his/her scenario is drag drop across applications. I BET thats it :)
    Code doesnt work for this scenario
  11. (no name)
    I need the code in the dragenter as the object is coming from outside the control. it is a problem with draggedElt but i dont understand why. I checked the object before and after the lines and it seems the same object except for the serializing. It works fine in marcelos code and I am thinking if somethings not getting updated correctly.
  12. Pavan Podila

    When you say its not working, is it the Drag preview that is failing? I feel that modifying the _draggedElt is the cause of the problems. Is it possible to move the code to DropTarget_PreviewDrop instead of using it inside the PreviewDragEnter?

  13. (no name)
    I have marcelos code and I am now using urs. I understand that the code might look smudgy but this is just the example of where its not working. I’ll mkae it generalized.
    When looking at the code with and without the lines, the draggedelement is the same.. the only thing being that one of them is serialised. why isnt this scenario working. Can you please try the above code and provide some advice.
    Thanks
  14. Pavan Podila

    You should not be modifying the DragDropManager for this kind of behavior. This code should be sitting inside the class that implements IDragSourceAdvisor. You also seem to be modifying the private variable _draggedElt, which is used at lot of other places inside the DragDropManager.

  15. (no name)
    i have added the following lines

    string serializedObject = e.Data.GetData("CanvasExample") as string;

    System.Xml.XmlReader reader = System.Xml.XmlReader.Create(new System.IO.StringReader(serializedObject));

    _draggedElt = System.Windows.Markup.XamlReader.Load(reader) as UIElement;

    to the function as provided in your code. This should result in the same behavior. However, drag drop doesnt work now. If i remove these lines the code works fine. Just wanted to know whats happenning.

  16. Pavan Podila

    It is unclear to me as to what is not working. Could you give me more information about your scenario?

  17. (no name)
    i am using the following code after modifying the manager a bit. i was using the code provided by lester on his blog and i used the code for your sample and it just doesnt work.. any hints
    static void DropTarget_PreviewDragEnter(object sender, DragEventArgs e)

    {

    if (UpdateEffects(sender, e) == false) return;

    string serializedObject = e.Data.GetData("CanvasExample") as string;

    System.Xml.XmlReader reader = System.Xml.XmlReader.Create(new System.IO.StringReader(serializedObject));

    _draggedElt = System.Windows.Markup.XamlReader.Load(reader) as UIElement;

    CreatePreviewAdorner();

    e.Handled = true;

    }

  18. (no name)
    Hi,
     I am trying to use this Concept with a Button which content is an Image and a Text. How should i modify it that it should support the drag and drop of the button visual Tree. Thanks in Advance. Pankaj Kashyap
  19. Pavan Podila

    Hello,
         If I understand correctly, you want to change the drag start point ??

  20. NiceOne

    This is very cool stuff, I finally understood DependencyProperties because of your article/code.

    One question: How can I make the drag start by leaving a small area around the drag start location instead of starting it by leaving the IDragSourceAdvisor element?

    Thanks for this great article!

  21. (no name)
    Very nice, I was able to instantly pop this into my app and I got drag drop to work instantly!  I’ve been trying to figure out how to get drag/drop working for a long time now and this helps a lot!  Thanks again!
  22. Pavin Podila: http://codeplex.com/FluidKit - WPF controls, etc... - Rob Relyea - Xamlified

    [...] ( Part 1, Part 2, Part 3, Part 4, Part 5, Part 6 [...]

  23. James

    Hi Pavan,

    Great code. Really loving Fluid Kit.

    I’m having one problem with the DragDropManager however. I’m using with a XamDataGrid from Infragistics with your code.

    I’ve had to code around some of the built in features of the Grid in order to get the DragDrupManager to work nicely with this grid. However I have one problem that I can’t seem to solve. The grid allows grouping of records I’ve added in logic to make the GroupByArea expand and collapse as it would on a grid without the manager plugged in, but I can’t seem to get the individual groups to expand once the records have been grouped.

    Do you have any ideas? Its like the DragDropManager is hijacking the events that would power the grids functionality. Ive tried conditionally marking the event as e.Handled = false but its still not allowing the normal grid events to function after evaluating if its a drag event or not.

    Any ideas would be great.

  24. Lynn SIm

    This realy simplifies the drag/drop process. However, your examples use XMAL code. I need to create new buttons and areas in C#. How do I set up a new advisor in C#? I’ve tried using Resource.Add, which it doesn’t complain about, but nothing happens.
    Thanks.

  25. sirkal

    Hi Pavan,

    I am reading your book on WPF Control Development. I am getitng to learn so much from your book. I have just transitioned to WPF from ASP.Net and still learning. I have to implement Drag and Drop events on my Tree view control following the MVVM pattern. In your book, in the attached properties chapter you provided a solutiuon for Drag and Drop using Canvas, Is it possible for you ro provide a similar example for TreeView Control.

    Thank you in advance.

Leave a Reply