Saturday, March 31, 2012
[2012.03.31] Visual Studio 2011 Beta took....
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.
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.
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
After that, the Template window will look like Fig. 4. Note that we have not yet based the $NAME$ on the $_NAMEFIELD$ variable yet.
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.
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]
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;45 namespace DragDropUsingMvvmLight016 {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 : DataTemplateSelector11 {12 public override DataTemplate SelectTemplate(object item, DependencyObject container)13 {14 var frameworkElement = container as FrameworkElement;1516 if (frameworkElement !=null && item is Contact )17 {18 Contact contact = item as Contact; // Contact is the type hosted in each ListBoxItem1920 if (contact.Crew == Faction.Strawhat)21 return (DataTemplate)frameworkElement.TryFindResource("SourceListBoxItemTemplate");22 return (DataTemplate)frameworkElement.TryFindResource("SourceListBoxDataTemplate02");23 }24 return null;25 }26 }27 }
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;45 namespace DragDropUsingMvvmLight016 {7 /// <summary>8 /// Extended class used to change the style of each ListBoxItem based on some custom requirement.9 /// </summary>10 public class ListBoxStyleSelectors : StyleSelector11 {12 public override Style SelectStyle(object item, DependencyObject container)13 {14 FrameworkElement frameworkElement = container as FrameworkElement;1516 if (frameworkElement != null && item is Contact)17 {18 Contact contact = item as Contact; // Contact is the type hosted in each ListBoxItem1920 if (contact.Crew == Faction.Strawhat)21 return (Style)frameworkElement.TryFindResource("SourceListBoxItemStyle1");22 return (Style)frameworkElement.TryFindResource("SourceListBoxItemStyle2");23 }24 return null;25 }26 }27 }
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.[2011.01.06] Drag and Drop in WPF using MVVMLight [III/IV]
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 DataTemplateBased 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;56 namespace DragDropUsingMvvmLight017 {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 : Adorner12 {13 private readonly ContentPresenter _contentPresenter;14 private readonly ListBoxItem _listBoxItem;15 private Point _updatedMousePosition;1617 public DraggedAdorner(UIElement adornedElement) : base(adornedElement)18 {19 _contentPresenter = new ContentPresenter();2021 _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 }2930 protected override Size MeasureOverride(Size constraint)31 {32 _contentPresenter.Measure(constraint);33 return _contentPresenter.DesiredSize;34 }3536 protected override Size ArrangeOverride(Size finalSize)37 {38 _contentPresenter.Arrange(new Rect(finalSize));39 return finalSize;40 }4142 protected override Visual GetVisualChild(int index)43 {44 return _contentPresenter;45 }4647 protected override int VisualChildrenCount48 {49 get { return 1; }50 }5152 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 }5859 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 adorner62 _updatedMousePosition = updatedPosition;63 var adornerLayer = AdornerLayer.GetAdornerLayer(elementToGetAdornerLayerFrom);64 if (adornerLayer != null)65 adornerLayer.Update(AdornedElement);66 }67 }68 }
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 Methods146147 /// <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 the153 /// 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 to159 var adornerLayer = AdornerLayer.GetAdornerLayer(elementToGetAdornerLayerFrom);160 _draggedAdorner = new DraggedAdorner(elementToAdorn);161 adornerLayer.Add(_draggedAdorner);162 }163164 /// <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);173174 if (adorners == null) return;175176 var dragAdorner = adorners[0] as DraggedAdorner;177 if (dragAdorner != null)178 {179 adornerLayer.Remove(dragAdorner);180 }181 }182 #endregion
Wednesday, January 5, 2011
[2011.01.05] Drag and Drop in WPF using MVVMLight [II/IV]
This is a multipart series outlining several topics is WPF
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:- Jaime Rodriguez - Drag and Drop in WPF Explained End to End Part 1 Part 2 Part 3
- Bea Stollnitz - How can I drag and drop items between data bound ItemsControls?
- Clarity Consulting - WPF Drag and Drop Between ItemsControls
- Code Project - WPF Drag-and-Drop Smorgasbord
- Marlon Grech - Drag Drop using Listboxes Part 1 Part 2
- Chrisitan Mosers - Drag and Drop in WPF
- Determine if you are initiating a drag/drop operation either as a combination of MouseLeftButtonDown and MouseMove or just MouseLeftButtonDown as I have done.
- 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.
- Add adorner: If you want to provide rich visual feedback during the drag operation, adding an adorner can be done at this stage.
- 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. - 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.
- 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.
- 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.
- 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.
- Remove Adorner: If you are using a custom adorner, once the drop is complete, this is a good place to remove it.
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>6061 <Border Grid.Row="2" Height="20" />6263 <TextBlock Grid.Row="3"64 Background="White"65 FontSize="18"66 Text="Listbox to drop to" />6768 <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>
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 listbox76 ListBox listBox = e.Source as ListBox;77 ListBoxItem listBoxItem = VisualHelper.FindAncestor<ListBoxItem>((DependencyObject)e.OriginalSource);7879 // set up shared states80 _listBoxItem = listBoxItem; // adorned element81 _topLevelGrid = GetTopLevelGrid(e); // element to get an adorner layer from higher up the logical tree than the listboxitem8283 // Find the data behind the listBoxItem84 if (listBox == null || listBoxItem == null) return;8586 Contact contact = (Contact)listBox.ItemContainerGenerator.ItemFromContainer(listBoxItem);8788 AddAdorner(listBoxItem, _topLevelGrid);8990 // Initialize the drag & drop operation91 DataObject dragData = new DataObject("myContactData", contact);92 DragDrop.DoDragDrop(listBoxItem, dragData, DragDropEffects.Move);93 }94 );
1 using System.Windows;2 using System.Windows.Media;34 namespace DragDropUsingMvvmLight015 {6 /// <summary>7 /// Walks up the visual tree to find the ancestor of a given type.8 /// </summary>9 internal static class VisualHelper10 {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 : DependencyObject18 {19 do20 {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 }
96 DragOverCommand = new RelayCommand<DragEventArgs>(97 e =>98 {99 if (!e.Data.GetDataPresent("contact"))100 {101 e.Effects = DragDropEffects.None;102 }103104 var currentMousePosition = e.GetPosition(_topLevelGrid);105106 if (_topLevelGrid != null && _draggedAdorner != null)107 _draggedAdorner.UpdateAdornerPosition(_topLevelGrid, currentMousePosition);108 }109 );
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;116117 _targetContacts.Add(contact); // add to new collection118 _contacts.Remove(contact); // remove from source collection119120 // pass in the root grid since its adorner layer was used to add ListBoxItems adorners to121 RemoveAdorner(_listBoxItem, _topLevelGrid);122 });123124 // if dropping on the source list, remove the adorner125 DropSourceCommand = new RelayCommand<DragEventArgs>(e => RemoveAdorner(_listBoxItem, _topLevelGrid));
