Thursday, January 6, 2011

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


downloadCode02
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

[2011.01.03].Drag.and.Drop.Using.MvvmLight.02
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;
5
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;
16
17 public DraggedAdorner(UIElement adornedElement) : base(adornedElement)
18 {
19 _contentPresenter = new ContentPresenter();
20
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 }
29
30 protected override Size MeasureOverride(Size constraint)
31 {
32 _contentPresenter.Measure(constraint);
33 return _contentPresenter.DesiredSize;
34 }
35
36 protected override Size ArrangeOverride(Size finalSize)
37 {
38 _contentPresenter.Arrange(new Rect(finalSize));
39 return finalSize;
40 }
41
42 protected override Visual GetVisualChild(int index)
43 {
44 return _contentPresenter;
45 }
46
47 protected override int VisualChildrenCount
48 {
49 get { return 1; }
50 }
51
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 }
58
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
146
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 }
163
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);
173
174 if (adorners == null) return;
175
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.

No comments: