Dragging WPF elements

This is the first in a series of post exploring that beloved UI gesture: dragging and dropping things around.

In this first post, I'll take a look at the simplest, most striaghtforward approach: adding a bunch of event handlers to your window and updating layout properties. I'll be using VB.NET - converting to C# should not be too difficult.

First, we'll start with this code in Window1.xaml.

<Window x:Class="Window1"
 Title="Drag Canvas" Height="300" Width="300">

  <Border DockPanel.Dock="Top" BorderBrush="DarkGray" BorderThickness="2" Padding="4">
   <TextBlock FontSize="8pt" FontFamily="Tahoma" TextWrapping="Wrap">
    <Bold>Drag Canvas Sample
    </Bold><LineBreak /><Run>
     This sample demonstrates the easiest way to drag shapes (or any other
     elements) on a Canvas area.</Run>

  <Canvas Name="MyCanvas">

      <GradientStop Color="White" Offset="0" />
      <GradientStop Color="DarkBlue" Offset="1" />

   <Rectangle Canvas.Top="8" Canvas.Left="8"
    Width="32" Height="32" Fill="LightPink" />
   <Ellipse Canvas.Top="8" Canvas.Left="48"
    Width="40" Height="16" Fill="Tan" />
   <Rectangle Canvas.Top="48" Canvas.Left="8"
    Width="32" Height="62" Fill="Green" />
   <Ellipse Canvas.Top="48" Canvas.Left="48"
    Width="62" Height="62" Fill="Orange" />



This creates a simple window that looks like this.

OK, now let's roll up our sleeves and build this bit by bit by adding code to Window1.xaml.vb.

First off, we'll start with some fields in Window1. Right under the constructor, we can add this bit of code.

Private m_StartPoint As Point ' Where did the mouse start off from?
Private m_OriginalLeft As Double ' What was the element's original x offset?
Private m_OriginalTop As Double ' What was the element's original y offset?
Private m_IsDown As Boolean ' Is the mouse down right now?
Private m_IsDragging As Boolean ' Are we actually dragging the shape around?
Private m_OriginalElement As UIElement ' What is it that we're dragging?
Private m_OverlayElement As Rectangle ' What is it that we're using to show where the shape will end up?

This is all information that we'll track while moving the shapes in the Canvas about.

Now, let's write the code to start the dragging. When the mouse goes down, we'll capture a some of the information we'll use, and then when the user begins to move the mouse, we'll add an element to show where the content will end up.

Private Sub MyCanvas_PreviewMouseLeftButtonDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles MyCanvas.PreviewMouseLeftButtonDown
 If MyCanvas Is e.Source Then
  Exit Sub
 End If

 m_IsDown = True
 m_StartPoint = e.GetPosition(MyCanvas)
 m_OriginalElement = e.Source
 e.Handled = True
End Sub

Private Sub MyCanvas_PreviewMouseMove(ByVal sender As Object, ByVal e As System.Windows.Input.MouseEventArgs) Handles MyCanvas.PreviewMouseMove
 If m_IsDown Then
  If Not m_IsDragging AndAlso _
   Math.Abs(e.GetPosition(MyCanvas).X - m_StartPoint.X) > SystemParameters.MinimumHorizontalDragDistance AndAlso _
   Math.Abs(e.GetPosition(MyCanvas).Y - m_StartPoint.Y) > SystemParameters.MinimumVerticalDragDistance Then
  End If
  If m_IsDragging Then
  End If
 End If
End Sub

A few things to note are the fact that we capture the mouse when it goes down (in case the user moves the mouse out of the bounding area), and the DragStarted() and DragMoved() helper methods I'm using. Also, notice how we use the system paramters to respect the user's selection for how far the mouse needs to move before we start dragging. This is especially helpful for laptops and tablets, on which it's very common to have the pointer shake a bit when working with the mouse stick thingy or a pen.

Now, what is it that we do in our helper methods?

Private Sub DragStarted()
 m_IsDragging = True

 m_OriginalLeft = Canvas.GetLeft(m_OriginalElement)
 m_OriginalTop = Canvas.GetTop(m_OriginalElement)

 ' Add a rectangle to indicate where we'll end up.
 ' We'll just use an alpha-blended visual brush.
 Dim brush As VisualBrush
 brush = New VisualBrush(m_OriginalElement)
 brush.Opacity = 0.5

 m_OverlayElement = New Rectangle()
 m_OverlayElement.Width = m_OriginalElement.RenderSize.Width
 m_OverlayElement.Height = m_OriginalElement.RenderSize.Height
 m_OverlayElement.Fill = brush

End Sub

Private Sub DragMoved()
 Dim currentPosition As Point = System.Windows.Input.Mouse.GetPosition(MyCanvas)
 Dim elementLeft As Double = (currentPosition.X - m_StartPoint.X) + m_OriginalLeft
 Dim elementTop As Double = (currentPosition.Y - m_StartPoint.Y) + m_OriginalTop
 Canvas.SetLeft(m_OverlayElement, elementLeft)
 Canvas.SetTop(m_OverlayElement, elementTop)
End Sub

Notice how I use a alpa-blended VisualBrush to show the user where the shape is going to end up. Also, because I'm working with a Canvas, I simply change the Left and Top attached properties. If this were a DockPanel, I might try seeing whether I would want to try snapping to an edge; or if this were a Grid, I might try to figure out in which cell the shape would drop. I'm just sticking to the simplest case in this sample, but you can take it as far as you want.

This is all well and good, but how do we actually drop the shape? Two typical gestures are releasing the mouse button, or pressing Escape (to cancel the operation). Let's code that up.

Private Sub MyCanvas_PreviewMouseLeftButtonUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles MyCanvas.PreviewMouseLeftButtonUp
 If m_IsDown Then
  e.Handled = True
 End If
End Sub

Private Sub Window1_PreviewKeyDown(ByVal sender As Object, ByVal e As System.Windows.Input.KeyEventArgs) Handles Me.PreviewKeyDown
 ' This is handled at the window level, because neither MyCanvas nor
 ' its children ever get keyboard focus.
 If e.Key = Input.Key.Escape AndAlso m_IsDragging Then
 End If
End Sub

Again, a helper method to make it easy to isolate what happens when we actually finish dragging, and here's the implementation for it.

Private Sub DragFinished(ByVal canceled As Boolean)
 If m_IsDragging Then
  If Not canceled Then
   Canvas.SetLeft(m_OriginalElement, Canvas.GetLeft(m_OverlayElement))
   Canvas.SetTop(m_OriginalElement, Canvas.GetTop(m_OverlayElement))
  End If
  m_OverlayElement = Nothing
 End If
 m_IsDragging = False
 m_IsDown = False
End Sub

And that, my dear readers, completes today's sample. Give it try, play around with it, marvel at the alpha-blended 'preview', and try extending it. Can you add other controls to drag around?

This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm.

Comments (4)

  1. Dmitry says:

    Hi Marcelo, thanks for a great article. I wonder is there any way to hide predefined drag cursors? I was trying to use old school UseDefaultCursors = false; but that didn’t work…


  2. MueMeister says:

    Hi Marcelo,

    thanks for the post but I have a question on dragging items on a listview. I have a ListView with a custom panel assigned to ListView.ItemsPanel (In former times one could assign it directly, now this is a ItemsPanelTemplate type and can only be attached through XAML). Anyway, If I want to provide such dragging feedback for my listview where do I start my approach? I mean, implementing all these mouse handlers on listview permits to change layout feedback on the panel which lays its children out. So I would implement all these handlers on the panel? Could you give an hint/approach of a solution where I can drag and drop items in a listview, let’s sy to sort items? Is it also possible to apply animation effects for user feedback?



  3. Also known as "how do I perform drag-drop between two data-bound list boxes"? Well, excellent question

Skip to main content