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 https://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 https://www.microsoft.com/info/cpyright.htm.
Comments
Anonymous
March 03, 2006
PingBack from http://www.schanda.co.za/blog/PermaLink.aspx?guid=04b1e07d-1dc7-4cb8-a218-a9a088fcd20cAnonymous
March 06, 2006
Senkwe has posted a bit of code over here with a creative way to achieve a similar effect to the recent&nbsp;drag/drop...Anonymous
March 08, 2006
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?Anonymous
March 08, 2006
doing a Mouse.Capture(null); in the previewMouseUp handler was necessary.. hope it helps some1 ;-)Anonymous
March 09, 2006
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!Anonymous
July 04, 2006
PingBack from http://www.netronproject.com/netron/?p=187Anonymous
October 19, 2006
In a post a few months ago, I included a sample that showed how to use an adorner to display a 'preview'Anonymous
February 01, 2008
Window Explorer Drag Image Introduction If you've worked with .NET drag and drop, you may have noticedAnonymous
February 12, 2008
Over on CodeProject.com (which has a bunch of WPF content), there are 2 new articles about building aAnonymous
February 16, 2008
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üllen kann! Dabei istAnonymous
February 18, 2008
Series Links This is part of a 3 part series: Shell Style Drag and Drop in .NET (WPF and WinForms) ShellAnonymous
February 18, 2008
Series Links This is part of a 3 part series: Shell Style Drag and Drop in .NET (WPF and WinForms) Shell