[2011.01.06] Drag and Drop in WPF using MVVMLight [III/IV]

2011-01-06 - C#, MVVM, WPF
[2011.01.06] Drag and Drop in WPF using MVVMLight [III/IV]
This is a multipart series outlining several topics is WPF.
  1. Part 1: MVVM via the MVVMLight framework
  2. Part 2: Drag and Drop
  3. Part 3: The Adorner
  4. Part 4: DataTemplateSelector, StyleSelector, Unit Testing

The Adorner

To provide a custom visual when doing the drag and drop, you can create an adorner. The look and feel of it can be anything you want, from a simple rectangle to something more complex. In this scenario, I want the dragged item to look exactly like the real item. First, recall that I am dragging the content of a ListBoxItem, not the actual ListBoxItem itself. The content of the ListBoxItem is a Contact type that is displayed using a DataTemplate. Think about how the Contact is shown in the ListBoxItem – if you look at the template in Blend, you will see as in Fig. 1, that each ListBoxItem contains nothing more than a Border and a ContentPresenter. This means that you can put whatever you want to be displayed in the ContentPresenter, which in this case will be a DataTemplate

Fig.1 A ListBoxItem template

Based on that, we can make the adorner a ContentPresenter also and just fill it with the appropriate DataTemplate when doing the drag. You first need to inherit from Adorner. Because you are creating a custom adorner, the doesn’t know much about it, so you have to instruct the system how to size and lay it out on the screen. You do this by overriding several methods and a property. These are all shown in Listing 1.

1 using System.Windows;

2 using System.Windows.Controls;

3 using System.Windows.Documents;

4 using System.Windows.Media;


6 namespace DragDropUsingMvvmLight01

7 {

8 /// <summary>

9 /// An extended adorner class used to attach a visual element to a ListBoxItem as it is being dragged.

10 /// </summary>

11 public class DraggedAdorner : Adorner

12 {

13 private readonly ContentPresenter _contentPresenter;

14 private readonly ListBoxItem _listBoxItem;

15 private Point _updatedMousePosition;


17 public DraggedAdorner(UIElement adornedElement) : base(adornedElement)

18 {

19 _contentPresenter = new ContentPresenter();


21 _listBoxItem = adornedElement as ListBoxItem;

22 if (_listBoxItem != null)

23 {

24 _contentPresenter.Content = _listBoxItem.Content;

25 _contentPresenter.ContentTemplateSelector = _listBoxItem.ContentTemplateSelector;

26 }

27 _contentPresenter.Opacity = 0.7;

28 }


30 protected override Size MeasureOverride(Size constraint)

31 {

32 _contentPresenter.Measure(constraint);

33 return _contentPresenter.DesiredSize;

34 }


36 protected override Size ArrangeOverride(Size finalSize)

37 {

38 _contentPresenter.Arrange(new Rect(finalSize));

39 return finalSize;

40 }


42 protected override Visual GetVisualChild(int index)

43 {

44 return _contentPresenter;

45 }


47 protected override int VisualChildrenCount

48 {

49 get { return 1; }

50 }


52 public override GeneralTransform GetDesiredTransform(GeneralTransform transform)

53 {

54 GeneralTransformGroup generalTransformGroup = new GeneralTransformGroup();

55 generalTransformGroup.Children.Add(new TranslateTransform(_updatedMousePosition.X, _updatedMousePosition.Y));

56 return generalTransformGroup;

57 }


59 public void UpdateAdornerPosition(Visual elementToGetAdornerLayerFrom, Point updatedPosition)

60 {

61 // save the new position of the mouse that is passed in as an arguement as you drag the adorner

62 _updatedMousePosition = updatedPosition;

63 var adornerLayer = AdornerLayer.GetAdornerLayer(elementToGetAdornerLayerFrom);

64 if (adornerLayer != null)

65 adornerLayer.Update(AdornedElement);

66 }

67 }

68 }

Listing 1: The DraggedAdorner class

The constructor of the DraggedAdorner class takes the element that is to be adorned. We need that to extract information from that we want to add to this adorner – namely it’s content. Notice that the ContentTemplateSelector property was also set. This was done because I have multiple DataTemplates that are used based on some custom logic. I will go into that later.
As its name implies, the UpdateAdornerPosition() method will take the current mouse position and call the Update() method. Notice in the GetDesiredTransform() method, I am adding a TranslateTransform to the transform group. This is what will update the position of the adorner as it is moved across the screen.
Adding and removing the adorner as the application runs is provided by utility methods in the MainViewModel class as shown in Listing 2.

145 #region Helper Methods


147 /// <summary>

148 /// Adds an adorner to an element.

149 /// </summary>

150 /// <param name="elementToAdorn">Element to add an adorner to.</param>

151 /// <param name="elementToGetAdornerLayerFrom">Element to get the AdornerLayer from.</param>

152 /// <example>In the case of the ListBox, if you want to adorn each ListBoxItem and the adorner layer of the

153 /// containing ListBox is used, then the adorner object gets clipped as it is moved out of the ListBox. In this case,

154 /// you need a layer that is higher up the visual tree to allow the adorner to be visible anywhere and not be clipped.

155 /// </example>

156 public void AddAdorner(UIElement elementToAdorn, Visual elementToGetAdornerLayerFrom)

157 {

158 // get the adorner layer to attach the adorner to

159 var adornerLayer = AdornerLayer.GetAdornerLayer(elementToGetAdornerLayerFrom);

160 _draggedAdorner = new DraggedAdorner(elementToAdorn);

161 adornerLayer.Add(_draggedAdorner);

162 }


164 /// <summary>

165 /// Removes an adorner from an element.

166 /// </summary>

167 /// <param name="adornedElement">The element that has an adorner associated with it.</param>

168 /// /// <param name="elementToGetAdornerLayerFrom">Element to get the AdornerLayer from.</param>

169 public void RemoveAdorner(UIElement adornedElement, Visual elementToGetAdornerLayerFrom)

170 {

171 var adornerLayer = AdornerLayer.GetAdornerLayer(elementToGetAdornerLayerFrom);

172 var adorners = adornerLayer.GetAdorners(adornedElement);


174 if (adorners == null) return;


176 var dragAdorner = adorners[0] as DraggedAdorner;

177 if (dragAdorner != null)

178 {

179 adornerLayer.Remove(dragAdorner);

180 }

181 }

182 #endregion

Listing 1: Methods to add and remove a DraggedAdorner

So at this stage there is an adorner and ways to add, update its position and remove it from the application. In addition, Part 2 of this series suggests where to perform each of these operations.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>