Update: There were some bugs in the code, which caused funny behavior with elements that have a render transform applied to them. I've updated the code below to fix these.

In part 1 we covered how the dragging behavior will be attached to the Canvas object. Let's take a look now at the actual dragging code.

Before we get to the actual code, we'll need some data members to keep some state between the events. These are mostly self explanatory, with the exception of _canvasLeft and _canvasTop. We'll use these to keep the original position of the element that we drag around in case we cancel the drag.

protected Canvas owner;
protected Point _lastLeftDown;
protected double _canvasLeft;
protected double _canvasTop;
protected bool _isDragging = false;

The code that support the dragging is split into two groups - first is the logic that determines that we are dragging something and how far away and the second is how we apply this to the actual Canvas children. Let's first look into how we determine we are dragging someting. We start on mouse left button down event. We check that the element generating the event belongs to the Canvas and that it has the IsDraggable property set on it. Then we save the original position of the element and capture the mouse. There's an additional check that this lbutton event is not a double click event.

public void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
   
FrameworkElement dragElement = e.Source as FrameworkElement;

    if ((dragElement == null) || (dragElement == this.owner))
       
return;

    if (!GetIsDraggable(dragElement))
       
return;

    // Track mouse click position for offsetting the drag element position
   
_lastLeftDown = e.GetPosition(this.owner);

    if ((e.ClickCount == 1) && (!_isDragging))
   
{
        // Capture mouse only on the left button down for a single click
       
// We want to avoid dragging on double click
       
dragElement.CaptureMouse();
    }
   
else
   
{
       
// Cancel any pending drag
       
StopDragging(dragElement, Mouse.GetPosition(this.owner), false);
    }
}

Obviously, on mouse left button up we need to stop dragging as well.

public void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    FrameworkElement dragElement = e.Source as FrameworkElement;

    if ((dragElement == null) || (dragElement == this.owner))
        return;

    StopDragging(dragElement, Mouse.GetPosition(this.owner), false);
}

The actual stop logic is also very simple. All it does is release the mouse capture and raise the DragCompletedEvent with the point to which we've dragged the element and whether the drag was canceled.

/// <summary>
/// Stop dragging, raise the drag completed event is necessary and release the mouse capture
/// </summary>
/// <param name="point">Device coodinates for the drag completed event</param>
/// <param name="canceled">True if drag was canceled</param>
protected void StopDragging(FrameworkElement dragElement, Point point, bool canceled)
{
    _isDragging =
false;

    if (Mouse.Captured == dragElement)
    {
       
// We still have the mouse capture, release it
       
dragElement.ReleaseMouseCapture();
    }

    // Signal end dragging
   
dragElement.RaiseEvent(new DragCompletedEventArgs(point.X - _lastLeftDown.X, point.Y - _lastLeftDown.Y, canceled));
}

Most of the actual drag logic is in the mouse move event. In this code we determnie two things - have we actually started a drag and how far have we dragged, if so. The first one is determined by checking if the mouse has moved more than an arbitrary number of pixels in any direction (and obviously you can tweak how precise that logic is) and if so, we actually flag a drag start and raise the DragStart event on the drag element. The second is based on whether we have detected a drag start already and whether it's still the same element that initially was dragged (to deal with the mouse moving outside of the Canvas or the window and other odd situations) and raising the DragDelta event on the drag element.

public void OnMouseMove(object sender, MouseEventArgs e)
{
   
FrameworkElement dragElement = e.Source as FrameworkElement;

    if ((dragElement == null) || (dragElement == this.owner))
       
return;

    Point point = Mouse.GetPosition(this.owner);

    if (_isDragging && ((Mouse.Captured != dragElement) || (Mouse.LeftButton != MouseButtonState.Pressed)))
    {
        StopDragging(dragElement, point,
true);
    }
   
else
   
{
       
if (!_isDragging && (Mouse.Captured == dragElement))
        {
           
if (!AreReallyClose(point, _lastLeftDown))
            {
               
// we are now officially dragging, raise drag start
               
_isDragging = true;

                dragElement.RaiseEvent(new DragStartedEventArgs(point.X - _lastLeftDown.X, point.Y - _lastLeftDown.Y));
           
}
        }

        if (_isDragging)
        {
           
// Raise the drag delta
           
dragElement.RaiseEvent(new DragDeltaEventArgs(point.X - _lastLeftDown.X, point.Y - _lastLeftDown.Y));
        }
    }
}

protected static bool AreReallyClose(Point p1, Point p2)
{
   
return (Math.Abs(p1.X - p2.X) < .001 && Math.Abs(p1.Y - p2.Y) < .001);
}

Now comes the second part of the drag logic. We have the drag events raised on the canvas child we are dragging. This enables us to let the element deal with the fact that it's being dragged around before the canvas gets those events. If the element does not do anything, the events are bubbled up. A normal Canvas panel doesn't handle these events, normally they would bubble up to the canvas parent. However, our drag helper has attached itself to the events on the canvas and can update the dragged element position. This approach gives us the flexibility to later one attach the helper to a more advanced Canvas descendant that knows how to deal with drag events and can choose to react to them or let them go to our handler.

protected void OnDragStarted(object sender, DragStartedEventArgs e)
{
   
FrameworkElement dragElement = e.Source as FrameworkElement;

    if ((dragElement == null) || (dragElement == this.owner))
       
return;

    _canvasLeft = Canvas.GetLeft(dragElement);
    if (Double.IsNaN(_canvasLeft))
        _canvasLeft = 0.0;

    _canvasTop = Canvas.GetTop(dragElement);
   
if (Double.IsNaN(_canvasTop))
        _canvasTop = 0.0;

    e.Handled = true;
}

protected void OnDragDelta(object sender, DragDeltaEventArgs e)
{
   
FrameworkElement dragElement = e.Source as FrameworkElement;

    if ((dragElement == null) || (dragElement == this.owner))
       
return;

    Canvas.SetLeft(dragElement, _canvasLeft + e.HorizontalChange);
   
Canvas.SetTop(dragElement, _canvasTop + e.VerticalChange);
    this.owner.UpdateLayout();

    e.Handled = true;
}

protected void OnDragCompleted(object sender, DragCompletedEventArgs e)
{
   
FrameworkElement dragElement = e.Source as FrameworkElement;

    if ((dragElement == null) || (dragElement == this.owner))
       
return;

    if (e.Canceled)
    {
    
    Canvas.SetLeft(dragElement, _canvasLeft);
       
Canvas.SetTop(dragElement, _canvasTop);
       
this.owner.UpdateLayout();
    }

    e.Handled = true;
}

Of course, there are some improvements that could be done here, like handling keyboard modifiers to the dragging or constraining the drag position of the element in the visible portion of the Canvas. However, these can be easily added to the existing logic.

I've attached the full class code to this post, so you don't have to assemble it yourself from the pieces in the two posts.