Click here to Skip to main content
15,868,016 members
Articles / Desktop Programming / WPF

Drag and Drop in MVVM WPF

Rate me:
Please Sign up or sign in to vote.
4.97/5 (16 votes)
3 Mar 2015CPOL9 min read 80.7K   3.5K   47   9
Dragging and dropping in WPF with a MVVM style architecture

Introduction

In this article I'll show how to implement drag and drop in WPF. We'll give the user a "ghosted" preview of the drag operation using a custom user control as the mouse cursor moves, and make the everything bindable so we can use MVVM. We'll provide some events so we can change the UI based on whether a drop operation is allowed, and aside from using MVVM Light for some shortcuts with our view model, this example will not rely on any third party libraries or packages.

Image 1

Background

An app I recently started working on has no buttons for performing logical operations. Instead, everything is accomplished via drag and drop. I was recently asked to put together a technical demo implementing this core UI feature in WPF.

WPF natively supports drag and drop operations - that is, you the developer get some information when a UI element is dragged across the screen and then dropped on another UI element, but from a user's perspective, there's no feedback. If we want some kind of visual cue, (mouse cursor change, ghosted preview, or moving the element itself) we the developers have to add that functionality.

Some of the other articles and examples I've seen provide drag preview operations by using a third party library. In an enterprise environment, we don't always have that luxury. 

So In this article, we'll implement a drag and drop gesture with a ghosted preview to add a new user to a list of users. Additionally, we'll animate a visual indicator on the ghosted image to indicate whether or not the drag/drop is allowed. And we'll do it all without relying on a third party library for our drag and drop support.

The code

This article is rather long, so I've grouped it into two sections

  1. Using the library
  2. Building the drag and drop helper library

Using the library

The first thing we need to do is add our preview user control - that is, the control that gets displayed while a drag operation is underway.

Yours can look however you want it to, but for mine, I'd like to have a user icon with a green plus if I can add it, and a red minus if I can't. 

Image 2     Image 3

Let's add a user control, and give it the following xaml:

XML
<Grid Name="grid">
    <Grid.RenderTransform>
        <TransformGroup>
            <ScaleTransform ScaleX="1" ScaleY="1"/>
            <SkewTransform AngleX="0" AngleY="0"/>
            <RotateTransform Angle="0"/>
            <TranslateTransform X="0" Y="0"/>
        </TransformGroup>
    </Grid.RenderTransform>
    <Image Name="imgIndicator" Source="user-icon.png" />
    <Rectangle Name="horizontalBar" Height="50" Width="150" Margin="140,191,10,59">
        <Rectangle.Fill>
            <SolidColorBrush Color="Green" />
        </Rectangle.Fill>
    </Rectangle>
    <Rectangle Name="verticalBar" Height="150" Width="50" Margin="190,140,60,10">
        <Rectangle.Fill>
            <SolidColorBrush Color="Green" />
        </Rectangle.Fill>
    </Rectangle>
</Grid>

In the resources for the user control, I'll add two animations:

The first one changes the horizontal and vertical bars to opaque and green when we can drop

XML
<Storyboard x:Key="canDropChanged" FillBehavior="HoldEnd">            
    <ColorAnimation To="Green" Storyboard.TargetName="horizontalBar" Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)" BeginTime="00:00:00" Duration="00:00:00.3" />
    <ColorAnimation To="Green" Storyboard.TargetName="verticalBar" Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)" BeginTime="00:00:00" Duration="00:00:00.3" />
            
    <DoubleAnimation BeginTime="00:00:00" Duration="00:00:00.25" AccelerationRatio=".1" DecelerationRatio=".9" To="1" Storyboard.TargetName="verticalBar" Storyboard.TargetProperty="(Rectangle.Opacity)" />
</Storyboard>

The second changes the opacity of the vertical bar, and color changes the horizontal to red when we can not drop:

XML
<Storyboard x:Key="cannotDropChanged" FillBehavior="HoldEnd">
    <ColorAnimation To="Red" Storyboard.TargetName="horizontalBar" Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)" BeginTime="00:00:00" Duration="00:00:00.3" />
    <ColorAnimation To="Red" Storyboard.TargetName="verticalBar" Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)" BeginTime="00:00:00" Duration="00:00:00.3" />
    <DoubleAnimation BeginTime="00:00:00" Duration="00:00:00.25" AccelerationRatio=".1" DecelerationRatio=".9" To="0" Storyboard.TargetName="verticalBar" Storyboard.TargetProperty="(Rectangle.Opacity)" />
</Storyboard>

You should note that the library I show how to build makes a hard reference to these storyboard names for transitioning between can and cannot drop states. In a future version, I'll probably change this over to a data trigger. Now. arguably, the color change on the vertical is not technially necessary, however, in my tests I think the fade to red on the horizontal bar looks better when it's not competing with the green on the color change.

Now that we've got the control which will serve as our preview, let's head over to the main window.

MainWindow.xaml

Let's start out by laying out the window:

XML
<Window x:Class="DragDropExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:dd="clr-namespace:DragDrop;assembly=DragDrop"      
        xmlns:controls="clr-namespace:DragDropExample"
        DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <ResourceDictionary>
            <controls:DragPreview x:Key="DragPreviewControl" />
        </ResourceDictionary>
    </Window.Resources>
    <Grid>
        <Canvas ZIndex="5" x:Name="OverlayCanvas" /><Grid></Grid>
    </Grid>
</Window>

In the library, our preview control is rendered on a Canvas that we specify at binding time - the overlay canvas will be that canvas. We use XAML's layout to ensure that the canvas covers the entire window, and set the z-index to make sure that anything we put on the canvas is rendered above our other controls.

Let's scroll on down to the <Border> element, and take a look at how we set up the drag source:

XML
<Border BorderBrush="Black" BorderThickness="1" Margin="0 10 0 0"
                   dd:DragDrop.IsDragSource="True"
                   dd:DragDrop.DropTarget="{Binding ElementName=dropPanel}"
                   dd:DragDrop.DragDropPreviewControl="{StaticResource DragPreviewControl}"
                   dd:DragDrop.DragDropPreviewControlDataContext="{Binding Source={StaticResource Locator},Path=DragPreview}"
                   dd:DragDrop.DragDropContainer="{Binding ElementName=OverlayCanvas}"
                   dd:DragDrop.ItemDropped="{Binding AddUser}"
                   >

All of the attached properties we declared in our DragDrop class are set up here. The item which you set as the drag source will be the item that originates the drag. For purposes of this article, I set the border element to be the drag source, but you can set it to be any control. 

  • The DropTarget is the control that recives the drop operation - there's nothing that we have to do to that control
  • The DragDropPreviewControl is our user control that gets displayed while a drag operation is underway
  • The DragDropPrewviewControlDataContext is the (optional) data context that gets assigned to to the preview control for databinding purposes while a drag operation is underway. If you omit this property, then DragDropPreviewControl gets this control's data context as its data context.
  • The DragDropContainer is the canvas which will render our preview control during the drag operation
  • The ItemDropped command is the command which will determine if we are allowed to drop, as well as serve as the event handler for when an item is dropped.

Let's now come over and build the library which provides the functionality.

Drag Drop Library

Let's start by setting up some infrastructure first.

DropState.cs

This is a quick enum to tell us what we can and can't do in terms of our drop operation

C#
public enum DropState
{
    CanDrop,
    CannotDrop
}
  • DropState.CanDrop means that the drag preview is inside the droppable area
  • DropState.CannotDrop means that the drag preview is outside the droppable area

DropEventArgs.cs

When we initiate a drop operation, we'll need some event args to pass to the caller. This is merely a quick wrapper around the data context so we can pass it as an event args object.

C#
/// <summary>
/// Event args for when a visual is dropped
/// </summary>
public class DragDropEventArgs : EventArgs
{
    public Object DataContext;
    public DragDropEventArgs() { }
    public DragDropEventArgs(Object dataContext)
    {
        DataContext = dataContext;
    }
}

DragDropPreviewBase.cs

DragDropPreviewBase serves as the base class for what we're going to show to the user while a drag operation is underway. Let's start by declaring the class, and adding some attributes to the render transform so that we have the ability to animate it when the control is loaded or disposed.

C#
public class DragDropPreviewBase : UserControl
{
    public DragDropPreviewBase()
    {
        ScaleTransform scale = new ScaleTransform(1f, 1f);
        SkewTransform skew = new SkewTransform(0f, 0f);
        RotateTransform rotate = new RotateTransform(0f);
        TranslateTransform trans = new TranslateTransform(0f, 0f);
        TransformGroup transGroup = new TransformGroup();
        transGroup.Children.Add(scale);
        transGroup.Children.Add(skew);
        transGroup.Children.Add(rotate);
        transGroup.Children.Add(trans);

        this.RenderTransform = transGroup;
    }
}

Next, we'll add a dependency property to this control which allows us to bind the DropState of this control to the data context

C#
#region DropState Dependency Property

#region Binding Property

/// <summary>
/// Gets and sets drop state for this drag and drop preview control
/// </summary>
public DropState DropState
{
    get { return (DropState)GetValue(DropStateProperty); }
    set { SetValue(DropStateProperty, value); }
}

#endregion

#region Dependency Property

/// <summary>
/// The backing <see cref="DependencyProperty"/> which enables animation, styling, binding, etc. for <see cref="DropState"/>
/// </summary>
public static readonly DependencyProperty DropStateProperty =
    DependencyProperty.Register("DropState", typeof(DropState), typeof(DragDropPreviewBase), new UIPropertyMetadata(DropStateChanged));

/// <summary>
/// Handles when drop state changes
/// </summary>
/// <param name="element">The <see cref="DependencyObject"/> that the attached <see cref="DependencyProperty"/>, <see cref="DropStateProperty"/>, is attched to.</param>
/// <param name="e"><see cref="DependencyPropertyChangedEventArgs"/> from the changed event</param>
public static void DropStateChanged(DependencyObject element, DependencyPropertyChangedEventArgs e)
{
    var instance = (DragDropPreviewBase)element;
    instance.StateChangedHandler(element, e);
}

public virtual void StateChangedHandler(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ }

#endregion

#endregion

This is pretty standard boilerplate code for a dependency property, so I'll continue on.

DragDrop.cs

Here's where we start to get going. DragDrop is what we'll use to indicate an object can be a drag source, move the DragDropPreviewBase, and invoke the ItemDropped command when the user releases the mouse over the eligible drop target. 

Let's start off by declaring the class, and adding two utility methods we'll be using later on:

C#
#region Utilities

/// <summary>
/// Walks the visual tree, and finds the ancestor of the <see cref="visual"/> which is an instance of <paramref name="ancestorType"/>
/// </summary>
/// <param name="ancestorType">The type of ancestor to look for in the visual tree</param>
/// <param name="visual">The object to start at in the visual tree</param>
/// <returns>The <see cref="FrameworkElement"/> which matches <paramref name="ancestorType"/></returns>
public static FrameworkElement FindAncestor(Type ancestorType, Visual visual)
{
    while (visual != null && !ancestorType.IsInstanceOfType(visual))
    {
        visual = (Visual)VisualTreeHelper.GetParent(visual);
    }
    return visual as FrameworkElement;
}

/// <summary>
/// Determines if the delta between two points exceeds the minimum horizontal and vertical drag distance, as defined by the system
/// </summary>
/// <param name="initialMousePosition">The starting position</param>
/// <param name="currentPosition">The current position</param>
/// <returns>True, if the delta exceeds the minimum horizontal and vertical drag distance</returns>
public static Boolean IsMovementBigEnough(Point initialMousePosition, Point currentPosition)
{
    return (Math.Abs(currentPosition.X - initialMousePosition.X) >= SystemParameters.MinimumHorizontalDragDistance ||
            Math.Abs(currentPosition.Y - initialMousePosition.Y) >= SystemParameters.MinimumVerticalDragDistance);
}

#endregion
  • FindAncestor does pretty much what you'd think it does; recursively walks the visual tree, and finds the ancesctor that's of the target type
  • IsMovementBigEnough determines if the delta between two points is larger than the system defined minimum. We'll need this later on to determine if it's necessary to actually proceed with a drag operation - we don't want to go through the hassle of performing the move if the user is only requesting a drag of 2 pixels (for example)

Next, we'll add the following attached properties. Since these are all implemented using the standard code snippet for attached properties, I'm going to omit pasting the code for these properties in the article.

  • DropTarget - The element which will recieve the drop
  • DragDropPreviewControlDataContext - the data context to associate with the drag and drop operation
  • DragDropPreviewControl - The DragDropPreviewBase user control which will be displayed as we move the mouse
  • DragDropContainer - The canvas which we use to absolutely position the preview control
  • ItemDropped - The ICommand which will execute when the control is dropped.

We need to add one more attached property, but before we do, we need to set up the instance variables. These values will allow us to capture the values of the attached properties, and store them for later use in the mouse down, mouse move, and mouse up events. When we move the mouse, we want all the values tied to an instance associated with that particular movement, and not accidentally reused if we initiate another drag and drop operation.

C#
private Window _topWindow;

/// <summary>
/// The location where we first started the drag operation
/// </summary>
private Point _initialMousePosition;

/// <summary>
/// The outmost canvas which the user can drag the <see cref="_dragDropPreviewControl"/>
/// </summary>
private Panel _dragDropContainer;

/// <summary>
/// The control which will serve as the drop arget
/// </summary>
private UIElement _dropTarget;

/// <summary>
/// Determines if we're currently tracking the mouse
/// </summary>
private Boolean _mouseCaptured;

/// <summary>
/// The control that's displayed (and moving with the mouse) during a drag drop operation
/// </summary>
private DragDropPreviewBase _dragDropPreviewControl;

/// <summary>
/// The data context of the <see cref="_dragDropPreviewControl"/>
/// </summary>
private Object _dragDropPreviewControlDataContext;

/// <summary>
/// The command to execute when items are dropped
/// </summary>
private ICommand _itemDroppedCommand;

private Point _delta;

#region Instance

/// <summary>
/// Lazy loaded backing member variable for <see cref="Instance"/>
/// </summary>
private static readonly Lazy<DragDrop> _Instance = new Lazy<DragDrop>(() => new DragDrop());

/// <summary>
/// Gets a static instance of <see cref="DragDrop"/>
/// </summary>
private static DragDrop Instance
{
    get { return _Instance.Value; }
}

#endregion

Now, let's add the IsDragSource attached property

First, let's get the dependency property added,

C#
#region IsDragSource Attached Property

#region Backing Dependency Property

/// <summary>
/// The backing <see cref="DependencyProperty"/> which enables animation, styling, binding, etc. for IsDragSource
/// </summary>
public static readonly DependencyProperty IsDragSourceProperty = DependencyProperty.RegisterAttached(
    "IsDragSource", typeof(Boolean), typeof(DragDrop), new PropertyMetadata(false, IsDragSourceChanged));

#endregion

The getter and setter

C#
/// <summary>
/// Gets the attached value of IsDragSource
/// </summary>
/// <param name="element">The <see cref="DependencyObject"/> that the attached <see cref="DependencyProperty"/>, <see cref="IsDragSourceProperty"/>, is attched to.</param>
/// <returns>The attached value</returns>
public static Boolean GetIsDragSource(DependencyObject element)
{
    return (Boolean)element.GetValue(IsDragSourceProperty);
}

/// <summary>
/// Sets the attached value of IsDragSource
/// </summary>
/// <param name="element">The <see cref="DependencyObject"/> that the attached <see cref="DependencyProperty"/>, <see cref="IsDragSourceProperty"/>, is attched to.</param>
/// <param name="value">the value to set</param>
public static void SetIsDragSource(DependencyObject element, Boolean value)
{
    element.SetValue(IsDragSourceProperty, value);
}

And the changed handler

C#
/// <summary>
/// Handles when <see cref="IsDragSourceProperty"/>'s value changes
/// </summary>
/// <param name="element">The <see cref="DependencyObject"/> that the attached <see cref="DependencyProperty"/>, <see cref="IsDragSourceProperty"/>, is attched to.</param>
/// <param name="e"><see cref="DependencyPropertyChangedEventArgs"/> from the changed event</param>
private static void IsDragSourceChanged(DependencyObject element, DependencyPropertyChangedEventArgs e)
{
    var dragSource = element as UIElement;
    if (dragSource == null)
    { return; }

    if (Object.Equals(e.NewValue, true))
    {
        dragSource.PreviewMouseLeftButtonDown += Instance.DragSource_PreviewMouseLeftButtonDown;
        dragSource.PreviewMouseLeftButtonUp += Instance.DragSource_PreviewMouseLeftButtonUp;
        dragSource.PreviewMouseMove += Instance.DragSource_PreviewMouseMove;
    }
    else
    {
        dragSource.PreviewMouseLeftButtonDown -= Instance.DragSource_PreviewMouseLeftButtonDown;
        dragSource.PreviewMouseLeftButtonUp -= Instance.DragSource_PreviewMouseLeftButtonUp;
        dragSource.PreviewMouseMove -= Instance.DragSource_PreviewMouseMove;
    }
}

Basically, If the dependency property changes to true, we'll wire up the event handlers, and if it changes to false, we uncouple them.

let's start by looking at what happens when we mouse down.

C#
/// <summary>
/// Tunneled event handler for when the mouse left button is about to be depressed
/// </summary>
/// <param name="sender">The object which invokes this event</param>
/// <param name="e">event args from the sender</param>
private void DragSource_PreviewMouseLeftButtonDown(Object sender, MouseButtonEventArgs e)
{
    try
    {
        var visual = e.OriginalSource as Visual;
        _topWindow = (Window)DragDrop.FindAncestor(typeof(Window), visual);

        _initialMousePosition = e.GetPosition(_topWindow);

        //first, determine if the outer container property is bound
        _dragDropContainer = DragDrop.GetDragDropContainer(sender as DependencyObject) as Canvas;

        if (_dragDropContainer == null)
        {
            //set the container to the canvas ancestor of the bound visual
            _dragDropContainer = (Canvas)DragDrop.FindAncestor(typeof(Canvas), visual);
        }

        _dropTarget = GetDropTarget(sender as DependencyObject);

        //get the data context for the preview control
        _dragDropPreviewControlDataContext = DragDrop.GetDragDropPreviewControlDataContext(sender as DependencyObject);

        if (_dragDropPreviewControlDataContext == null)
        { _dragDropPreviewControlDataContext = (sender as FrameworkElement).DataContext; }

        _itemDroppedCommand = DragDrop.GetItemDropped(sender as DependencyObject);

    }
    catch (Exception exc)
    {
        Console.WriteLine("Exception in DragDropHelper: " + exc.InnerException.ToString());
    }
}

What we're doing here is getting our references to the objects we referenced using the dependency properties, and tying them to the member variables of the current instance. It's not much, but it sets us up for the mouse move event.

C#
/// <summary>
/// Tunneled event handler for when the mouse is moving
/// </summary>
/// <param name="sender">The object which invokes this event</param>
/// <param name="e">Event args from the sender</param>
private void DragSource_PreviewMouseMove(Object sender, MouseEventArgs e)
{
    if (_mouseCaptured || _dragDropPreviewControlDataContext == null)
    {
        return; //we're already capturing the mouse, or we don't have a data context for the preview control
    }

    if (DragDrop.IsMovementBigEnough(_initialMousePosition, e.GetPosition(_topWindow)) == false)
    {
        return; //only drag when the user moved the mouse by a reasonable amount
    }

    _dragDropPreviewControl = (DragDropPreviewBase)GetDragDropPreviewControl(sender as DependencyObject);
    _dragDropPreviewControl.DataContext = _dragDropPreviewControlDataContext;
    _dragDropPreviewControl.Opacity = 0.7;

    _dragDropContainer.Children.Add(_dragDropPreviewControl);
    _mouseCaptured = Mouse.Capture(_dragDropPreviewControl); //have the preview control recieve and be able to handle mouse events

    //offset it just a bit so it looks like it's underneath the mouse
    Mouse.OverrideCursor = Cursors.Hand;

    Canvas.SetLeft(_dragDropPreviewControl, _initialMousePosition.X - 20);
    Canvas.SetTop(_dragDropPreviewControl, _initialMousePosition.Y - 15);

    _dragDropContainer.PreviewMouseMove += DragDropContainer_PreviewMouseMove;
    _dragDropContainer.PreviewMouseUp += DragDropContainer_PreviewMouseUp;

}

So this is where it gets interesting - when we move the mouse cursor by a significant amount, we'll get the preview control, attach its desired datacontext for MVVM, set its opacity to 70% so it appears that it's being "ghosted"; but once that's done, this future mouse movement events generated by the drag source will bail out early by design.

Even though the mouse movement event is still being originated by the drag source, we're now moving the ghosted preview over the canvas; so it's really more appropriate that the canvas should be handling the preview mouse move event and not the drag source. So, we'll capture that tunneled event calling Mouse.Capture on the movent of the preview control. We also need to know when to stop handling it, so we'll attach a handler to the preview mouse up event as well.

There's a lot happening in the DragDropContainer_PreviewMouseMove event, so let's step through it.

C#
/// <summary>
/// Tunneled event handler for when the mouse is moving
/// </summary>
/// <param name="sender">The object which invokes this event</param>
/// <param name="e">Event args from the sender</param>
private void DragDropContainer_PreviewMouseMove(Object sender, MouseEventArgs e)
{
    var currentPoint = e.GetPosition(_topWindow);

    //offset it just a bit so it looks like it's underneath the mouse
    Mouse.OverrideCursor = Cursors.Hand;
    currentPoint.X = currentPoint.X - 20;
    currentPoint.Y = currentPoint.Y - 15;

    _delta = new Point(_initialMousePosition.X - currentPoint.X, _initialMousePosition.Y - currentPoint.Y);
    var target = new Point(_initialMousePosition.X - _delta.X, _initialMousePosition.Y - _delta.Y);

    Canvas.SetLeft(_dragDropPreviewControl, target.X);
    Canvas.SetTop(_dragDropPreviewControl, target.Y);

    _dragDropPreviewControl.DropState = DropState.CannotDrop;

    if (_dropTarget == null)
    {
        AnimateDropState();
        return;
    }

so first thing we do is get the X and Y of where our mouse is currently located, and then shift it up and to the left just a bit so that the top left corner appears underneath the mouse. Then, physically move it on the canvas by using the Canvas.SetLeft and Canvas.SetTop attached properties. Now that we've moved it, if the configuration doesn't give us anywhere to drop it, then animate the drop state to a "cannotDrop" animation, and return.

Next, we'll determine if our mouse is over the drop target.

C#
var transform = _dropTarget.TransformToVisual(_dragDropContainer);
var dropBoundingBox = transform.TransformBounds(new Rect(0, 0, _dropTarget.RenderSize.Width, _dropTarget.RenderSize.Height));

if (e.GetPosition(_dragDropContainer).X > dropBoundingBox.Left &&
    e.GetPosition(_dragDropContainer).X < dropBoundingBox.Right &&
    e.GetPosition(_dragDropContainer).Y > dropBoundingBox.Top &&
    e.GetPosition(_dragDropContainer).Y < dropBoundingBox.Bottom)
{
    _dragDropPreviewControl.DropState = DropState.CanDrop;
}

WPF's coordinate system for left, right top and bottom of a given control are relative to the container control. Since the drop target might (and most likely is not) using the same relative coordinates as our pointer, we need to normalize these to the same coordinate system. We do that by calling TransformToVisual.

Now that the coordinate system is normalized, we draw a bounding box, and determine if the pointer is currently located inside the box. If it is, then we'll set the drop state to CanDrop.

One last thing - Even though we're now physically holding the mouse over the drop target, we still might not be allowed to drop it - that's where our commanding comes in.

C#
//bounding box might allow us to drop, but now we need to check with the command
if (_itemDroppedCommand != null && _itemDroppedCommand.CanExecute(_dragDropPreviewControlDataContext) == false)
{
    _dragDropPreviewControl.DropState = DropState.CannotDrop; //commanding trumps visual.                                    
}

If commanding says we can't drop, then we set the drop state to CannotDrop. Commanding trumps visual.

When we release the mouse, there's some things we need to do:

C#
/// <summary>
/// Event handler for when the mouse button is released in the context of the drag and drop preview
/// </summary>
/// <param name="sender">The object which invokes this event</param>
/// <param name="e">event args from the sender</param>
private void DragDropContainer_PreviewMouseUp(Object sender, MouseEventArgs e)
{
    switch (_dragDropPreviewControl.DropState)
    {
        case DropState.CanDrop:
            try
            {
                //some animation code removed for article

                var canDropSb = new Storyboard() { FillBehavior = FillBehavior.Stop };
                canDropSb.Children.Add(scaleXAnim);
                canDropSb.Children.Add(scaleYAnim);
                canDropSb.Children.Add(opacityAnim);
                canDropSb.Completed += (s, args) => { FinalizePreviewControlMouseUp(); };

                canDropSb.Begin(_dragDropPreviewControl);

                if (_itemDroppedCommand != null)
                { _itemDroppedCommand.Execute(_dragDropPreviewControlDataContext); }
            }
            catch (Exception ex)
            { }
            break;
        case DropState.CannotDrop:
            try
            {
                //some animation code removed for article

                var cannotDropSb = new Storyboard() { FillBehavior = FillBehavior.Stop };
                cannotDropSb.Children.Add(translateXAnim);
                cannotDropSb.Children.Add(translateYAnim);
                cannotDropSb.Children.Add(opacityAnim);
                cannotDropSb.Completed += (s, args) => { FinalizePreviewControlMouseUp(); };

                cannotDropSb.Begin(_dragDropPreviewControl);
            }
            catch (Exception ex) { }
            break;
    }

    _dragDropPreviewControlDataContext = null;
    _mouseCaptured = false;
}

Depending on the control's drop state, do a little animation, and then call FinalizePreviewControlMouseUp, which removes the preview from the canvas, uncouples the handlers, and returns the cursor to normal.

There's one thing left, and that's to handle the preview mouse up event on the control that initiated the drag:

C#
/// <summary>
/// Tunneled event handler for when the dragged item is released
/// </summary>
/// <param name="sender">The object which invokes this event</param>
/// <param name="e">Event args from the sender</param>
private void DragSource_PreviewMouseLeftButtonUp(Object sender, MouseButtonEventArgs e)
{
    _dragDropPreviewControlDataContext = null;
    _mouseCaptured = false;

    if (_dragDropPreviewControl != null)
    { _dragDropPreviewControl.ReleaseMouseCapture(); }
}

We need to release the mouse capture on the drag preview control so it no longer signals the mouse events.

 

Credit

Lee Roth for the idea of using a canvas to position user controls

History

2015-03-03 : Initial post

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Matthew is a software developer currently living and working in San Antonio Texas. When not working on software, Matthew enjoys spending time in his backyard garden.

Comments and Discussions

 
QuestionWindows 10 not working? Pin
Member 1019550513-Aug-21 2:00
professionalMember 1019550513-Aug-21 2:00 
QuestionLots of code errors Pin
Member 1356402024-Apr-18 3:57
Member 1356402024-Apr-18 3:57 
QuestionVery great job Pin
Member 1297456421-Feb-18 12:51
Member 1297456421-Feb-18 12:51 
PraiseExcellent article Pin
Member of Code Project :)29-Aug-17 10:38
Member of Code Project :)29-Aug-17 10:38 
QuestionDrop on a ItemsControl Pin
harrison-matt30-May-17 2:29
harrison-matt30-May-17 2:29 
GeneralMy vote of 5 Pin
Remi Lebrun5-Mar-15 10:58
Remi Lebrun5-Mar-15 10:58 
GeneralRe: My vote of 5 Pin
MatthewThomas5-Mar-15 14:31
MatthewThomas5-Mar-15 14:31 
QuestionFantastic Pin
RemkoH3-Mar-15 22:25
RemkoH3-Mar-15 22:25 
AnswerRe: Fantastic Pin
MatthewThomas4-Mar-15 2:23
MatthewThomas4-Mar-15 2:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.