Sunday, May 17, 2015

[2015.05.17] Moving to sdasrath.com

I am moving this blog to my own domain, etc

http://sdasrath.com

Additionally, I am updating links to zip files on my new site. It's a work in progress. Email me if something is not working (sdasrath@outlook.com)

Saturday, March 31, 2012

[2012.03.31] Visual Studio 2011 Beta took....

...forever to install. About 1hr 15mins by my count. Time to finally give this thing a try in a non-VM environment.

Monday, February 21, 2011

[2011.02.21] ReSharper C# snippet for MVVM ViewModel Property creation

Here is a simple C# snippet for ReSharper (should work on most versions) to simplify the creation of ViewModel properties. It is clear that creating properties in the ViewModel can be a tedious and repetitive process, especially when there are several involved. To create the snippet, you need to add a new Live Template in R#.

In Visual Studio under the ReSharper menu item, select LiveTemplates... and you will see Fig. 1.

[2011.02.21].ReSharper.snippet.01
Fig.1 The Template Explorer window

Under the User Templates node, select the New Template option from the toolbar and you will be taken to the Template creation page. Here you can enter the shortcut used to toggle the snippet and a description. I used vmp (ViewModel Property) for my shortcut here. Simply copy and paste the code below into the window and I will explain the changes you need to make.

private $TYPE$ _$NAMEFIELD$;

$END$

public $TYPE$ $NAME$

{

    get {return _$NAMEFIELD$; }

    set

    {

        if(_$NAMEFIELD$ == value) return;

        _$NAMEFIELD$ = value;

        OnPropertyChanged("$NAME$");

    }

}

The artifacts enclosed between dollar signs ($) can be thought of as variables that you can replace once the snippet has been activated. If we follow the naming convention of labeling fields starting with an underscore and all properties begin with uppcase letter, you will see that if you leave things the way they are as in Fig. 2, you will not get the anticipated result.

[2011.02.21].ReSharper.snippet.02
Fig.2 Editing the template

This is where the options in the right pane of the Template window comes into play. There are built in macros to help us get the correct set up. We want the $NAME$ to basically be like $_NAMEFIELD$ but without the underscore and the first letter must be upper case. So click the "Choose macro" option next to the NAME identifier in the right pane and from the list, as in Fig. 3, select:
Value of another variable with the first character in upper case

[2011.02.21].ReSharper.snippet.03
Fig.3 Macro selection

After that, the Template window will look like Fig. 4. Note that we have not yet based the $NAME$ on the $_NAMEFIELD$ variable yet.

[2011.02.21].ReSharper.snippet.04
Fig.4 After setting the macro

Now click the another variable link and you will get a dropdown list to select which variable $NAME$ should use as its basis. This is shown in Fig. 5.

[2011.02.21].ReSharper.snippet.05
Fig.5 Relating variables

Your snippet is now ready for action!

NOTE: OnPropertyChanged should be replaced by whatever method name you used when you implemented INotifyPropertyChanged. For instance, you may have called yours RaisePropertyChanged instead of OnPropertyChanged, so modify the snippet to best suit your needs.

Thursday, January 6, 2011

[2011.01.06] Drag and Drop in WPF using MVVMLight [IV/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

In this final part, I will outline some other features that supplement the application and provides a better user experience. This was done primarily as a demonstration of the features, namely DataTemplateSelector and StyleSelector.

The Data Template Selector

You use a DataTemplateSelector to display data from the same collection differently, based on some criteria in the data. For example, the Contact type has a property, Crew, which is a Faction enumeration. Please take a look at the Contact class in the code to download to see the definitions. In this case, I will use different DataTemplates based on which Faction each member belongs to. The different templates are defined in the ResourceDictionary1.xaml file.
You need to inherit from from the DataTemplateSelector base class and override the SelectTemplate() method. This is all illustrated in Listing 1.

1 using System.Windows;
2 using System.Windows.Controls;
3 using DragDropUsingMvvmLight01.Model;
4
5 namespace DragDropUsingMvvmLight01
6 {
7 /// <summary>
8 /// Extended class used to change the data template of each ListBoxItem based on some custom requirement.
9 /// </summary>
10 public class FactionDataTemplateSelector : DataTemplateSelector
11 {
12 public override DataTemplate SelectTemplate(object item, DependencyObject container)
13 {
14 var frameworkElement = container as FrameworkElement;
15
16 if (frameworkElement !=null && item is Contact )
17 {
18 Contact contact = item as Contact; // Contact is the type hosted in each ListBoxItem
19
20 if (contact.Crew == Faction.Strawhat)
21 return (DataTemplate)frameworkElement.TryFindResource("SourceListBoxItemTemplate");
22 return (DataTemplate)frameworkElement.TryFindResource("SourceListBoxDataTemplate02");
23 }
24 return null;
25 }
26 }
27 }
Listing 1: Implementation of a DataTemplateSelector

The Style Selector

The reason and strategy follows the same pattern as in the template selector case, so I will just add the code to Listing 2 and let you take it from there.
1 using System.Windows;
2 using System.Windows.Controls;
3 using DragDropUsingMvvmLight01.Model;
4
5 namespace DragDropUsingMvvmLight01
6 {
7 /// <summary>
8 /// Extended class used to change the style of each ListBoxItem based on some custom requirement.
9 /// </summary>
10 public class ListBoxStyleSelectors : StyleSelector
11 {
12 public override Style SelectStyle(object item, DependencyObject container)
13 {
14 FrameworkElement frameworkElement = container as FrameworkElement;
15
16 if (frameworkElement != null && item is Contact)
17 {
18 Contact contact = item as Contact; // Contact is the type hosted in each ListBoxItem
19
20 if (contact.Crew == Faction.Strawhat)
21 return (Style)frameworkElement.TryFindResource("SourceListBoxItemStyle1");
22 return (Style)frameworkElement.TryFindResource("SourceListBoxItemStyle2");
23 }
24 return null;
25 }
26 }
27 }
Listing 2: Implementation of a StyleSelector

Unit Testing

I am brand spanking new at this. I only did tests for the Contact model as I was not sure how to test the view models. I used NUnit as my testing framework. Thus far, I did not have to use any stubs or mocks. See Listing 3 for the code.


1 using System.Collections.ObjectModel;
2 using NUnit.Framework;
3 using DragDropUsingMvvmLight01.Model;
4
5 namespace DragDropUsingMvvmLight01.Tests
6 {
7 [TestFixture]
8 public class ContactTests
9 {
10 private Contact _contact = null;
11 private ObservableCollection<Contact> _contacts;
12
13 #region Setup
14 [SetUp]
15 public void Setup()
16 {
17 _contact = new Contact();
18 _contacts = _contact.GetContacts();
19 }
20 #endregion
21
22 [Test]
23 public void GetContacts_ReturnValueIsNotNull_ReturnsTrue()
24 {
25 Assert.That(_contacts, Is.Not.Null);
26 }
27
28 [Test]
29 public void GetContacts_IsTypeofContact_ReturnsTrue()
30 {
31 Assert.That(_contact, Is.InstanceOf<Contact>());
32 }
33
34 [Test]
35 public void GetContacts_HasAtLeastOneItemInCollection_ReturnsTrue()
36 {
37 Assert.That(_contacts.Count, Is.GreaterThanOrEqualTo(1));
38 }
39
40 [Test]
41 public void GetContacts_ObjectIsInGoodState_ReturnsTrue()
42 {
43 var contact = _contacts[0];
44 var firstNamePropertyIsNotNullOrEmpty = contact.Name;
45 var lastNamePropertyIsNotNullOrEmpty = contact.Alias;
46 var phonePropertyIsAValidNumber = contact.Race;
47 var profileImageUrlPropertyIsNotNullOrEmpty = contact.ProfileImageUrl;
48 Assert.That(string.IsNullOrEmpty(firstNamePropertyIsNotNullOrEmpty), Is.False);
49 Assert.That(string.IsNullOrEmpty(lastNamePropertyIsNotNullOrEmpty), Is.False);
50 Assert.That(phonePropertyIsAValidNumber, Is.Not.NaN);
51 Assert.That(string.IsNullOrEmpty(profileImageUrlPropertyIsNotNullOrEmpty), Is.False);
52
53 }
54
55 #region Teardown
56 [TearDown]
57 public void Teardown()
58 {
59 _contact = null;
60 }
61 #endregion
62 }
63 }
64

Listing 3: Unit tests for the Contact model
The code looks mangled here due to a width of 460px and the long names of the test methods.

[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.

Wednesday, January 5, 2011

[2011.01.05] Drag and Drop in WPF using MVVMLight [II/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

Drag and Drop

MSDN has a section called Drag and Drop Overview but as far as I can tell, it only outlines what to do with the data you are moving around. You can also get more D&D info from:
Christian Mosers's post on WPF Tutorial.net has the steps you need to follow to do Drag and Drop. I did mine slightly differently and I will list (re-hash) the steps he outlined basically, but for my scenario.
  1. Determine if you are initiating a drag/drop operation either as a combination of MouseLeftButtonDown and MouseMove or just MouseLeftButtonDown as I have done.
  2. Determine what data is to be dragged and store that in a DataObject by passing in a format and the data to the constructor. See MSDN's Drag and Drop Overview for more information regarding DataObject.
  3. Add adorner: If you want to provide rich visual feedback during the drag operation, adding an adorner can be done at this stage.
  4. Call the static DragDrop.DoDragDrop() passing in the source of the drag (a ListBoxItem in this case), the actual data to be dragged and an appropriate DragDropEffects enumeration [see MSDN].
    Note: Steps 1-4 can all be done in the MouseLeftButtonDown handler as I have outlined in the demo.
  5. Set the AllowDrop property on elements that you want to be able to drop on. In this case, I made both the source and target ListBoxs droppable. The property is set in the MainWindow.xaml file.
  6. Handle either the DragEnter or DragOver event. Test in this handler if you are passing over an element that has it's AllowDrop property set and that is accepts the type of data that is being dragged. Do this check by calling the GetDataPresent() method on the DragEventArgs parameter.
  7. Update Adorner: If you are using a custom adorner, this is a good place to update its position on the screen as it is dragged around.
  8. Finally, handle the Drop event when the user releases the mouse button. If the drop is done on a target that is expecting it and the correct data format is used, then retrieve the dragged DataObject and do whatever you need to with it at this point. You get the data by accessing the Data property on the DragEventArgs parameter and calling the GetData() method.
  9. Remove Adorner: If you are using a custom adorner, once the drop is complete, this is a good place to remove it.
Fig. 1 shows what the application looks like.

[2011.01.03].Drag.and.Drop.Using.MvvmLight.01
Fig.1 Drag and Drop

Let's take a closer look at each of the main points in the drag and drop operation. Listing 1 shows the markup with the ListBox elements and the associated events that necessary for D&D. Notice the use of the EventToCommand behavior here.

36 <ListBox x:Name="dragSource"
37 Grid.Row="1"
38 MinWidth="100"
39 MinHeight="40"
40 Margin="0,0,0,0"
41 AllowDrop="True"
42 Background="AliceBlue"
43 ItemContainerStyleSelector="{DynamicResource styleSelector}"
44 ItemsPanel="{DynamicResource SourceListBoxItemsPanelTemplate}"
45 ItemsSource="{Binding Contacts}"
46 ItemTemplateSelector="{DynamicResource dataTemplateSelector}"
47 Style="{DynamicResource ListBoxStyle1}">
48 <i:Interaction.Triggers>
49 <i:EventTrigger EventName="PreviewMouseLeftButtonDown">
50 <cmd:EventToCommand Command="{Binding PreviewMouseLeftButtonDownCommand, Mode=OneWay}" PassEventArgsToCommand="True" />
51 </i:EventTrigger>
52 <i:EventTrigger EventName="DragOver">
53 <cmd:EventToCommand Command="{Binding DragOverCommand, Mode=OneWay}" PassEventArgsToCommand="True" />
54 </i:EventTrigger>
55 <i:EventTrigger EventName="Drop">
56 <cmd:EventToCommand Command="{Binding DropSourceCommand, Mode=OneWay}" PassEventArgsToCommand="True" />
57 </i:EventTrigger>
58 </i:Interaction.Triggers>
59 </ListBox>
60
61 <Border Grid.Row="2" Height="20" />
62
63 <TextBlock Grid.Row="3"
64 Background="White"
65 FontSize="18"
66 Text="Listbox to drop to" />
67
68 <ListBox x:Name="dropTarget"
69 Grid.Row="4"
70 MinWidth="100"
71 MinHeight="117"
72 Margin="0,0,0,0"
73 AllowDrop="True"
74 Background="#FFE4F3FD"
75 ItemContainerStyleSelector="{DynamicResource styleSelector}"
76 ItemsPanel="{DynamicResource SourceListBoxItemsPanelTemplate}"
77 ItemsSource="{Binding TargetContacts}"
78 ItemTemplateSelector="{DynamicResource dataTemplateSelector}"
79 Style="{DynamicResource ListBoxStyle1}">
80 <i:Interaction.Triggers>
81 <i:EventTrigger EventName="Drop">
82 <cmd:EventToCommand Command="{Binding DropTargetCommand, Mode=OneWay}" PassEventArgsToCommand="True" />
83 </i:EventTrigger>
84 <i:EventTrigger EventName="DragOver">
85 <cmd:EventToCommand Command="{Binding DragOverCommand, Mode=OneWay}" PassEventArgsToCommand="True" />
86 </i:EventTrigger>
87 </i:Interaction.Triggers>
88 </ListBox>
Listing 1: Markup showing the ListBoxs and the events used for Drag and Drop

Steps 1-7: Drag

The PreviewMouseLeftButtonDown is a good place to determine if you want to begin a drag operation. The logic of what happens in this event is shown in Listing 2. In this case, I want to drag the data in a ListBoxItem, so first I needed to get a reference to the ListBoxItem that fired the mouse event as shown in Lines 76-77.
72 PreviewMouseLeftButtonDownCommand = new RelayCommand<MouseButtonEventArgs>(
73 e =>
74 {
75 // get dragged listbox
76 ListBox listBox = e.Source as ListBox;
77 ListBoxItem listBoxItem = VisualHelper.FindAncestor<ListBoxItem>((DependencyObject)e.OriginalSource);
78
79 // set up shared states
80 _listBoxItem = listBoxItem; // adorned element
81 _topLevelGrid = GetTopLevelGrid(e); // element to get an adorner layer from higher up the logical tree than the listboxitem
82
83 // Find the data behind the listBoxItem
84 if (listBox == null || listBoxItem == null) return;
85
86 Contact contact = (Contact)listBox.ItemContainerGenerator.ItemFromContainer(listBoxItem);
87
88 AddAdorner(listBoxItem, _topLevelGrid);
89
90 // Initialize the drag & drop operation
91 DataObject dragData = new DataObject("myContactData", contact);
92 DragDrop.DoDragDrop(listBoxItem, dragData, DragDropEffects.Move);
93 }
94 );
Listing 2: Command to deal with the dragging part of the operation.
In Line 77, I am using a helper method to actually pinpoint the correct ListBoxItem as shown in Listing 3. To create a DataObject, I first needed to grab the data contained in the ListBoxItem as shown in Line 86 by calling the ItemFromContainer method on the ItemContainerGenerator property of the ListBox. In Line 88, the adorner I used to help visualize the dragging operation is added. Lines 91-92 creates the DataObject to be dragged and then call the DoDragDrop method.
1 using System.Windows;
2 using System.Windows.Media;
3
4 namespace DragDropUsingMvvmLight01
5 {
6 /// <summary>
7 /// Walks up the visual tree to find the ancestor of a given type.
8 /// </summary>
9 internal static class VisualHelper
10 {
11 /// <summary>
12 /// Recursive method to walk up the visual tree to return an ancestor type of the supplied type.
13 /// </summary>
14 /// <typeparam name="T">Type of ancestor to search for.</typeparam>
15 /// <param name="current">Type to start search from.</param>
16 /// <returns></returns>
17 internal static T FindAncestor<T>(DependencyObject current) where T : DependencyObject
18 {
19 do
20 {
21 if (current is T)
22 {
23 return (T)current;
24 }
25 current = VisualTreeHelper.GetParent(current);
26 } while (current != null);
27 return null;
28 }
29 }
30 }
Listing 3: Helper method to find an element up the visual tree hierarchy.
Notice that both ListBoxes refer to the same DragOver event. This is because in both cases, all I am doing is checking to see if the dragged item is over an element it can drop on and at the same time, update the position of the adorner as it is moved across the screen. See Listing 4 for all this fun stuff
96 DragOverCommand = new RelayCommand<DragEventArgs>(
97 e =>
98 {
99 if (!e.Data.GetDataPresent("contact"))
100 {
101 e.Effects = DragDropEffects.None;
102 }
103
104 var currentMousePosition = e.GetPosition(_topLevelGrid);
105
106 if (_topLevelGrid != null && _draggedAdorner != null)
107 _draggedAdorner.UpdateAdornerPosition(_topLevelGrid, currentMousePosition);
108 }
109 );
Listing 4: Checking for a viable drop target and updating the adorner position.

Steps 8-9: Drop

Each ListBox raises its own version of the Drop event. Lines 111-122 deals with the expected case of dragging and dropping from source to target. First, check the DragEventArgs parameter to see if there is data there in the format we are expecting. This is shown in Line 114. If there is correct data, then simply update the collections of Contact items that is data bound to the respective ListBox. If there is a successful drop, then at this point, remove the adorner.

In Line 125 which deals with the case of doing a drag/drop on the source ListBox, there isn't much to do other than remove the adorner. See all this in Listing 5.
111 DropTargetCommand = new RelayCommand<DragEventArgs>(
112 e =>
113 {
114 if(!e.Data.GetDataPresent("myContactData")) return;
115 Contact contact = e.Data.GetData("myContactData") as Contact;
116
117 _targetContacts.Add(contact); // add to new collection
118 _contacts.Remove(contact); // remove from source collection
119
120 // pass in the root grid since its adorner layer was used to add ListBoxItems adorners to
121 RemoveAdorner(_listBoxItem, _topLevelGrid);
122 });
123
124 // if dropping on the source list, remove the adorner
125 DropSourceCommand = new RelayCommand<DragEventArgs>(e => RemoveAdorner(_listBoxItem, _topLevelGrid));
Listing 5: The drop operation.