Showing drag/drop feedback on the WPF adorner layer


Why adorners?
In the last sample, I showed how to allow the users to drag and drop shapes in a Canvas, and how to create a preview effect for the operation. One problem with this code is that it only works on Canvas panels.


In this post, we'll look into using the adorner layer in the window to draw our preview, so it works with any panel.


Cosmetic changes
OK, so what has changed? First, let's look at some cosmetic stuff.


I changed my top TextBlock to read as follows.


<TextBlock FontSize="8pt" FontFamily="Tahoma" TextWrapping="Wrap">
 <Bold>Drag Canvas Sample
 </Bold><LineBreak /><Run>
  This sample demonstrates using adorners to drag shapes (or any other element).</Run>
</TextBlock>


I also added a TextBox in the Canvas, just to show off a bit.


<TextBox Canvas.Top="120" Canvas.Left="8" />


Creating a new Adorner
Now, let's create a new class of Adorner, that we'll use for our preview effect. To do this, I simply declared an inner class within Window1.


Class DropPreviewAdorner
 Inherits Adorner


 ' Adding some basic fields to help us keep track of where we are and what we render
 Private m_Child As Rectangle
 Private m_LeftOffset As Double
 Private m_TopOffset As Double



 ' Methods will come here.
 ' ...
End Class


The first to do when subclassing the Adorner class is to add the appropriate constructor.


Public Sub New(ByVal adornedElement As UIElement)
 MyBase.New(adornedElement)


 Dim brush As VisualBrush = New VisualBrush(adornedElement)


 m_Child = New Rectangle()
 m_Child.Width = adornedElement.RenderSize.Width
 m_Child.Height = adornedElement.RenderSize.Height
 m_Child.Fill = brush
End Sub


Now, many adorners will simply render their own content. Because we're lazy and we're using a Rectangle instead, we need to make sure the layout system knows about this and lays it out appropriately (we'll simply size to content).


Protected Overrides Function MeasureOverride(ByVal constraint As System.Windows.Size) As System.Windows.Size
 m_Child.Measure(constraint)
 Return m_Child.DesiredSize
End Function


Protected Overrides Function ArrangeOverride(ByVal finalSize As System.Windows.Size) As System.Windows.Size
 m_Child.Arrange(New Rect(finalSize))
 Return finalSize
End Function


Protected Overrides Function GetVisualChild(ByVal index As Integer) As System.Windows.Media.Visual
 Return m_Child
End Function


Protected Overrides ReadOnly Property VisualChildrenCount() As Integer
 Get
  Return 1
 End Get
End Property


With this bit of code, we can already show the rectangle we wanted.


We'll want to allow the drag/drop code we wrote in the window to update the adorner to follow the mouse, so we'll add a couple of properties for this.


Public Property LeftOffset() As Double
 Get
  Return m_LeftOffset
 End Get
 Set(ByVal value As Double)
  m_LeftOffset = value
  UpdatePosition()
 End Set
End Property


Public Property TopOffset() As Double
 Get
  Return m_TopOffset
 End Get
 Set(ByVal value As Double)
  m_TopOffset = value
  UpdatePosition()
 End Set
End Property


Private Sub UpdatePosition()
 Dim adornerLayer As AdornerLayer = Me.Parent
 If Not adornerLayer Is Nothing Then
  adornerLayer.Update(AdornedElement)
 End If
End Sub


Finally, adorners are always placed relative to the element they adorn. You can then place them relative to the corners, the middle, off to the side, within, etc. We'll just offset the adorner to where the user would like to drop the dragged element, by adding a translate transform to whatever was necessary to get to the adorned element.


Public Overrides Function GetDesiredTransform(ByVal transform As System.Windows.Media.GeneralTransform) As System.Windows.Media.GeneralTransform
 Dim result As GeneralTransformGroup = New GeneralTransformGroup()
 result.Children.Add(MyBase.GetDesiredTransform(transform))
 result.Children.Add(New TranslateTransform(LeftOffset, TopOffset))
 Return result
End Function


Using our new Adorner
To leverage our new adorner, we simply need to update the drag/drop code we wrote in the last sample. First, let's change the type of the overlay element.


Private m_OverlayElement As DropPreviewAdorner


Now, when we start dragging, instead of creating a rectangle, we'll create our adorner.


Private Sub DragStarted()
 m_IsDragging = True


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


 Dim layer As AdornerLayer
 m_OverlayElement = New DropPreviewAdorner(m_OriginalElement)
 layer = AdornerLayer.GetAdornerLayer(m_OriginalElement)
 layer.Add(m_OverlayElement)
End Sub


When we finish, we'll use the offsets we store in the adorner to update the position of the moved element, and we'll also remove the adorner from the tree.


Private Sub DragFinished(ByVal canceled As Boolean)
 System.Windows.Input.Mouse.Capture(Nothing)
 If m_IsDragging Then
  AdornerLayer.GetAdornerLayer(m_OverlayElement.AdornedElement).Remove(m_OverlayElement)
  If Not canceled Then
   Canvas.SetLeft(m_OriginalElement, m_OriginalLeft + m_OverlayElement.LeftOffset)
   Canvas.SetTop(m_OriginalElement, m_OriginalTop + m_OverlayElement.TopOffset)
  End If
  m_OverlayElement = Nothing
 End If
 m_IsDragging = False
 m_IsDown = False
End Sub


And, finally, while we are dragging, we'll update our new adorner instead of the old rectangle.


Private Sub DragMoved()
 Dim currentPosition As Point = System.Windows.Input.Mouse.GetPosition(MyCanvas)
 m_OverlayElement.LeftOffset = currentPosition.X - m_StartPoint.X
 m_OverlayElement.TopOffset = currentPosition.Y - m_StartPoint.Y
End Sub


Was that easy or what? Now you can run the example, and you can again drag and drop everything. For fun, try changing the Canvas to a DockPanel. Because we haven't written any code to accomodate this layout, the dragging and dropping won't behave as expected, but you'll still be able to see the adorner correctly following the mouse.


Final touch
As a final touch, we can animate the opacity of the visual brush we're using - this is very simple to do. Just add the following lines in the constructor for our DropPreviewAdorner to add a local animation.


Public Sub New(ByVal adornedElement As UIElement)
  ...
 m_Child.Fill = brush


 Dim animation As System.Windows.Media.Animation.DoubleAnimation
 animation = New System.Windows.Media.Animation.DoubleAnimation(0.3, 1, New Duration(TimeSpan.FromSeconds(1)))
 animation.AutoReverse = True
 animation.RepeatBehavior = System.Windows.Media.Animation.RepeatBehavior.Forever
 brush.BeginAnimation(System.Windows.Media.Brush.OpacityProperty, animation)
End Sub


Now you should get a cool fade-in / fade-out effect on the preview for the drop operation.



Update on 2006-10-19: to read about a gotcha in GetDesiredTransform, read http://blogs.msdn.com/marcelolr/archive/2006/10/19/wpf-dragdrop-sample-and-viewbox-beware.aspx 


Update on 2010-04-28: updated links to MSDN - latest version used when needed, but you can see this has been around for a while.


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 (12)

  1. Senkwe has posted a bit of code over here with a creative way to achieve a similar effect to the recent&amp;nbsp;drag/drop…

  2. Ruurd Boeke says:

    Built a c# project based on this, and it’s very cool.

    My draghost (in your sample a canvas, in my project a stackpanel or a grid) defines the mousehandlers. The first time I drag an item, all is well, however, the second time I try to drag, the e.Source from the mouseargs in the preview left mouse down event, are the same as the draghost!

    In the handler, we write (if e.Source == draghost) return;

    Thus, nothing happens..

    It is really weird that the first time, the element is the source, and the second time the containing panel. If I switch to another app, and then back, it will work again.

    any ideas?

  3. Ruurd Boeke says:

    doing a Mouse.Capture(null); in the previewMouseUp handler was necessary.. hope it helps some1 😉

  4. Ruurd, you are right – that’s the right place to put that bit of code. I obviously messed something up while posting it from my project.

    Thanks for pointing this out!

  5. In a post a few months ago, I included a sample that showed how to use an adorner to display a ‘preview’

  6. Window Explorer Drag Image Introduction If you’ve worked with .NET drag and drop, you may have noticed

  7. Rob Relyea says:

    Over on CodeProject.com (which has a bunch of WPF content), there are 2 new articles about building a

  8. Nachdem es jetzt auch bei uns rasant in Richtung Windows Presentation Framework (wpf) geht, habe ich heute mehrere Stunden damit verbracht Informationen zu sammeln, wie ich diverse Anforderungen an neue Anwendungen in wpf erf&uuml;llen kann! Dabei ist

  9. Series Links This is part of a 3 part series: Shell Style Drag and Drop in .NET (WPF and WinForms) Shell

  10. Series Links This is part of a 3 part series: Shell Style Drag and Drop in .NET (WPF and WinForms) Shell

Skip to main content