Archive
WPF, MVVM and the TreeView Control using with different HierarchicalDataTemplates and DataTemplateSelector – Reloaded
In this previous post I outlined a way to work with the TreeView control in an MVVM fashion. In order to make any sense of this post you will need to refer to the previous one.
The code, in order to keep it simple, was quite verbose.
This post is really to outline a shorter, more generic, but more complex version of the hierarchy building code as per Listing 1 below.
public class HierarchyViewModel : ViewModelBase { public CollectionView Customers { get; private set; } private HierarchyItemViewModel _selectedItem; public HierarchyViewModel(List customers, object selectedEntity) { var customerHierarchyItemsList = BuildHierarchyList<Customer>(customers, ((a, b) => a.Number == b.Number), selectedEntity, x => BuildHierarchyList<Order>(x.Orders, ((a, b) => a.Number == b.Number), selectedEntity, y => BuildHierarchyList<Product>(y.Products, ((a, b) => a.GetHashCode() == b.GetHashCode()), selectedEntity, null) ) ); this.Customers = new CollectionView(customerHierarchyItemsList); // select the selected item and expand it's parents if (_selectedItem != null) { _selectedItem.IsSelected = true; HierarchyItemViewModel current = _selectedItem.Parent; while (current != null) { current.IsExpanded = true; current = current.Parent; } } } private List<HierarchyItemViewModel> BuildHierarchyList<T>(List<T> sourceList, Func<T, T, bool> isSelected, object selectedEntity, Func<T, List<HierarchyItemViewModel>> getChildren) { List<HierarchyItemViewModel> result = new List<HierarchyItemViewModel>(); foreach (T item in sourceList) { // create the hierarchy item and add to the list var hierarchyItem = new HierarchyItemViewModel(item); result.Add(hierarchyItem); // check if this is the selected item if (selectedEntity != null && selectedEntity.GetType() == typeof(T) && (isSelected.Invoke(item, (T)selectedEntity))) { _selectedItem = hierarchyItem; } if (getChildren != null) { var children = getChildren.Invoke(item); children.ForEach(x => x.Parent = hierarchyItem); hierarchyItem.Children = new CollectionView(children); } } return result; } }
Listing 1
Final thoughts…
Comments are welcome on how to approve this further. My intention was to create a Type keyed dictionary of definitions that contained Func for finding the children and IComparers for deciding if the item is selected. This dictionary of definitions could then be passed in to the method that builds the tree which would make it even cleaner and better able to cope with changes in the hierarchy. However time prevented me from finalising this. Maybe later…
Downloads
WilberBeast.TreeView.Demo.zip (100.70 kb)
WilberBeast.TreeView.Demo.doc (100.70 kb)
Same as the zip version, just renamed to .doc to help with downloading.
CompositeCollection Binding Problem – It’s not part of the Visual Tree
XAML is fantastic, but occasionally unpredictable which can cause some confusion.
One instance of this is the CompositeCollection tag which is used to build up a collection of values from a number of sources.
An example of this might be as simple as adding a “Please choose…” to the top of a drop down list or more complex scenarios, such as building up a list of products from different suppliers. In most cases, when using MVVM these kind of situations can be handled in the View Model, but even so, it can still be useful to build up lists in the XAML.
One such example I hit recently was creating a context menu for filtering on a data grid. I wanted to filter based on the values in the column, but also add two sorting options at the top of the list. A simplified version of the XAML is shown in Listing 1 below.
<ContextMenu.ItemsSource> <CompositeCollection> <MenuItem Header="Sort Ascending" /> <MenuItem Header="Sort Descending" /> <Separator /> <CollectionContainer Collection="{Binding Path=FilterOptions}}" /> </CompositeCollection> </ContextMenu.ItemsSource>
Listing 1 – Simplified code to show an example usage of CompositeCollection
However if you try to use code such as this you’ll very quickly hit the following binding problem.
Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=FilterOptions; DataItem=null; target element is ‘CollectionContainer’ (HashCode=23627591); target property is ‘Collection’ (type ‘IEnumerable’)
Why? Well it is all down to the fact that the data context of the XAML, specified at the top level that is inherited down through the elements actually goes down the Visual Tree. The killer is that the CompositeCollection, having no visual properties, is not part of the visual tree and therefore does not inherit the data context. The CompositeCollection has no clue what “FilterOptions” is in this example and thus throws a binding error.
Luckily we can easily solve this as all elements have access to the static resources of the XAML so we can easily create a bridging static resource that does the binding that we need and then use that within the composite collection. Listing 2 shows the static resource that we need in this instance.
<Window.Resources> <CollectionViewSource x:Key="FilterOptionsBridge" Source="{Binding Path=Filters}" /> </Window.Resources>
Listing 2 – Static resource bridging back to the data context
And finally we can adjust our CollectionContainer to point to the static resource, as in Listing 3.
... <CollectionContainer Collection="{Binding Source={StaticResource FilterOptionsBridge}}" /> ...
Listing 3 – Update to reference the static resource
As a side note, the CollectionViewSource tag needs to bind to an IEnumerable, so if you are binding to a CollectionView or one of the derived classes you’ll need to change the binding to be to the SourceCollection property as shown in Listing 4.
<Window.Resources> <CollectionViewSource x:Key="FilterOptionsBridge" Source="{Binding Path=Filters.SourceCollection}" /> </Window.Resources>
Listing 4 – Updated binding to reference the SourceCollection
Code Snippet – proprpc
Anyone who has played around with WPF or Silverlight for while will have come across the INotifyPropertyChanged interface and the RaisePropertyChanged method.
In an earlier post I highlighted one possible solution, though the use of an extension method, for calling the RaisePropertyChanged method without having to use “magic strings”.
I now use this extensively in my day to day WPF work, however I still sometimes forget to include the RaisePropertyChanged call in the property setter which then takes some time to debug.
In order to avoid this I’ve finally created a code snippet that will insert a field backed property that also calls RaisePropertyChanged.
The snippet is called ‘proprpc‘ so that it is line with the other property snippets of ‘prop‘, ‘propdp‘, ‘propfull‘, etc.
Download links are given below for the .snippet file (including a zipped version) and a Visual Studio Installer file.
WPF, MVVM and the TreeView Control using with different HierarchicalDataTemplates and DataTemplateSelector
I spent some time today getting to grips with the TreeView control whilst trying to maintain an MVVM perspective.
To generalise the situation I have a pretty typical hierarchy of objects, e.g. Customer, Order, Product, each of which has numerous properties.
Although the objects contain the hierarchical nature within themselves, i.e. Customer has a property called Orders, the objects themselves do not contain properties useful for displaying with a TreeView (or other such hierarchical control), such as IsSelected, IsExpanded, etc.
What I want to be able to achieve is to be able to expose the objects via the ViewModel to the View so that each type of object can be displayed differently, e.g. the Customer shows a customer icon and displays the customer number and name, whereas the Order shows an order icon and displays the order number and total value. In addition I need to add the additional properties to each that will be required for use in a hierarchical control, such as the TreeView.
The options at this point are:
- Subclass each object individually and add in the extra properties
This means that the implementation is very much tied to this solution and is not very reusable. - Create a new set of new objects (one for each of the original objects), containing the additional properties required, that accept the original objects in the constructor and then exposes only the properties required for display.
This again ties the implementation to the solution, but also limits any future changes to the view as to what can be exposed. - Wrap the objects in a class that has the required additional properties suitable for a hierarchy
This is the most reusable and generic solution, but of course brings with it some additional problems. - Implement a pattern, such as the Decorator pattern.
Sadly since the origin of the classes is out of my control this is not an option. This can happen when importing proxies from various WCF services, utilising classes generated by Entity Framework, etc.
I opted for the third option, creating a wrapper, which makes life a little harder, i.e. is it not a quick and dirty solution, but it has the advantage of reuse.
The Hierarchy Item Wrapper (HierarchyItemViewModel)
The first item we will create here is the wrapper for the objects to be shown in the TreeView. I’ll call this ‘HierarchyItemViewModel’.
In my implementation this inherits from ViewModelBase. ViewModelBase simply sets up some of the key points required in an MVVM solution, such as ObservableObjects. See the MVVM Foundation for a good starting point.
The main points in the code shown below in Listing 1 are:
- The constructor accepts an object which is the data item being wrapped. This is then exposed to the View via the DataItem property.
- A CollectionView of Children is exposed as a property as well as Parent.
- The IsSelected and IsExpanded properties that we needed are created.
- The properties call the RaisePropertyChanged method on which I have written about previously.
public class HierarchyItemViewModel : ViewModelBase { private CollectionView _children; private bool _isExpanded; private bool _isSelected; public HierarchyItemViewModel(object dataItem) { DataItem = dataItem; } public object DataItem { get; private set; } public HierarchyItemViewModel Parent { get; set; } public bool IsExpanded { get { return _isExpanded; } set { _isExpanded = value; RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName()); } } public bool IsSelected { get { return _isSelected; } set { _isSelected = value; RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName()); } } public System.Windows.Data.CollectionView Children { get { return _children; } set { _children = value; } } }
Listing 1 – HierarchyItemViewModel
The Hierarchy View Model (HierarchyViewModel)
Now we have our wrapper for the items we need to create our nested set of items ready to expose to the actual TreeView.
Listing 2, below, shows the code that builds the hierarchy taking in a list of Customer objects in the constructor. We are assuming that we have already obtained from elsewhere the actual data we want to display. The code is a bit long winded to keep it simple. For a more complex but more generic version see this post.
So here are we are going to wrap each item in a HierarchyItemViewModel object and create the parent and child relationships.
In addition, since we actually want to select a certain item from the list and make sure that it is visible we look out for our chosen item, identified in the constructor, which in this case I have called selectedEntity.
public class HierarchyViewModel : ViewModelBase { public CollectionView Customers { get; private set; } private HierarchyItemViewModel _selectedItem; public HierarchyViewModel(List customers, object selectedEntity) { // create the top level collectionview for the customers var customerHierarchyItemsList = new List(); foreach (Customer c in customers) { // create the hierarchy item and add to the list var customerHierarchyItem = new HierarchyItemViewModel(c); customerHierarchyItemsList.Add(customerHierarchyItem); // check if this is the selected item if (selectedEntity != null && selectedEntity.GetType() == typeof(Customer) && (selectedEntity as Customer).Equals(c)) { _selectedItem = customerHierarchyItem; } // if there are any orders in customerHierarchyItem if (c.Orders.Count != 0) { // create a new list of HierarchyItems var orderHierarchyItemsList = new List(); // loop through the orders and add them foreach (Order o in c.Orders) { // create the hierarchy item and add to the list var orderHierarchyItem = new HierarchyItemViewModel(o); orderHierarchyItem.Parent = customerHierarchyItem; orderHierarchyItemsList.Add(orderHierarchyItem); // check if this is the selected item if (selectedEntity != null && selectedEntity.GetType() == typeof(Order) && (selectedEntity as Order).Equals(o)) { _selectedItem = orderHierarchyItem; } // if there are any products in orderHierarchyItem if (o.Products.Count != 0) { // create a new list of HierarchyItems var productHierarchyItemsList = new List(); // loop through the sites and add them foreach (Product p in o.Products) { // create the hierarchy item and add to the list var productHierarchyItem = new HierarchyItemViewModel(p); productHierarchyItem.Parent = orderHierarchyItem; productHierarchyItemsList.Add(productHierarchyItem); // check if this is the selected item if (selectedEntity != null && selectedEntity.GetType() == typeof(Product) && (selectedEntity as Product).Equals(p)) { _selectedItem = productHierarchyItem; } } // create the children of the order orderHierarchyItem.Children = new CollectionView(productHierarchyItemsList); } } // create the children of the customer customerHierarchyItem.Children = new CollectionView(orderHierarchyItemsList); } } this.Customers = new CollectionView(customerHierarchyItemsList); // select the selected item and expand it's parents if (_selectedItem != null) { _selectedItem.IsSelected = true; HierarchyItemViewModel current = _selectedItem.Parent; while (current != null) { current.IsExpanded = true; current = current.Parent; } } } }
Listing 2 – HierarchyViewModel
The TreeView XAML
The next step is to create our TreeView in XAML and bind it to our HierarchyViewModel. For the simplicity of this example I’ve added the TreeView class straight to the MainWindowView. This could of course be wrapped up into its own UserControl. The ViewModel is attached to the DataContext of the View on creation. I won’t go into the code for how to bind the ViewModel to the View as this is covered in depth elsewhere. Important points to note in the code in Listing 3 are:
- The TreeView tag (line 11) where the DataContext is set for the TreeView, which identifies from where the data for the TreeView will be sourced.
- The three HierarchicalDataTemplate items, one for each of Customer, Order and Product. Note that each binds to different properties within the DataItem and each could have its own styling. This is the key point of this example.
- The Window.Resources (line 8) where the HierarchyDataTemplateSelector is identified. This essentially provides a key which can be used in the XAML to a method that selects which HierarchicalDataTemplate will be used for each item in the TreeView. I’ll cover this in more depth below,
- Again, in the TreeView tag (line 11) the ItemTemplateSelector which references the hierarchyDataTemplateSelector mentioned above.
<Window x:Class="WilberBeast.TreeView.Demo.Views.MainWindowView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ViewModels="clr-namespace:WilberBeast.TreeView.Demo.ViewModels" xmlns:View.Helper="clr-namespace:WilberBeast.TreeView.Demo.Views.Helper" Title="MainWindowView" Height="350" Width="525"> <Window.Resources> HierarchyDataTemplateSelector x:Key="hierarchyDataTemplateSelector" /> </Window.Resources> <Grid> <TreeView Name="HierarchyTreeView" DataContext="{Binding Path=HierarchyViewModel}" ItemTemplateSelector="{StaticResource hierarchyDataTemplateSelector}" ItemsSource="{Binding Customers}"> <TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="IsExpanded" Value="{Binding IsExpanded}" /> <Setter Property="IsSelected" Value="{Binding IsSelected}" /> <Setter Property="FontWeight" Value="Normal" /> <Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="FontWeight" Value="Bold" /> </Trigger> </Style.Triggers> </Style> </TreeView.ItemContainerStyle> <TreeView.Resources> <HierarchicalDataTemplate x:Key="CustomerTemplate" DataType="{x:Type ViewModels:HierarchyItemViewModel}" ItemsSource="{Binding Path=Children}"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=DataItem.Name}" Margin="0 0 5 0"/> <TextBlock Text="{Binding Path=DataItem.Number}"/> <!--<span class="hiddenSpellError" pre=""-->StackPanel> <!--<span class="hiddenSpellError" pre=""-->HierarchicalDataTemplate> OrderTemplate" DataType="{x:Type ViewModels:HierarchyItemViewModel}" ItemsSource="{Binding Path=Children}"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=DataItem.Number}"/> </StackPanel> </HierarchicalDataTemplate> ProductTemplate" DataType="{x:Type ViewModels:HierarchyItemViewModel}"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=DataItem.Name}"/> </StackPanel> </HierarchicalDataTemplate> </TreeView.Resources> </TreeView> </Grid> </Window>
Listing 3 – TreeView XAML
The DataTemplateSelector
Since to the TreeView class the all the objects within the TreeView appear to be of the same type, due to each being wrapped up in the HierarchyItemViewModel class, in order to display the Customer, Order and Product objects with different styles and content we have to have some code to help the TreeView identify which HierarchicalDataTemplate to use in each case. This is where the TemplateSelector comes in. This is nothing new or fancy, just one of the those hidden gems of WPF. The code for the HierarchyDataTemplateSelector is shown in Listing 4below. The code is fairly self explanatory. Basically the class inherits from the base class DataTemplateSelector and overrides the virtual method SelectTemplate. The TreeView passes the items one at a time to this method and the return value is simply the name of the HierarchicalDataTemplate to use.
public class HierarchyDataTemplateSelector : DataTemplateSelector { public override DataTemplate SelectTemplate(object item, DependencyObject container) { DataTemplate retval = null; FrameworkElement element = container as FrameworkElement; if (element != null && item != null && item is HierarchyItemViewModel) { HierarchyItemViewModel hierarchyItem = item as HierarchyItemViewModel; if (hierarchyItem.DataItem != null) { if (hierarchyItem.DataItem.GetType() == typeof(Customer)) { retval = element.FindResource("CustomerTemplate") as DataTemplate; } else if (hierarchyItem.DataItem.GetType() == typeof(Order)) { retval = element.FindResource("OrderTemplate") as DataTemplate; } else if (hierarchyItem.DataItem.GetType() == typeof(Product)) { retval = element.FindResource("ProductTemplate") as DataTemplate; } } } return retval; } }
Listing 4 – HierarchyDataTemplateSelector
All the code used in this example has been pulled together into one downloadable project that demonstrates the principle. The links are below.
Please do send me your comments as they are always welcome.
Happy coding…
WilberBeast.TreeView.Demo.zip (100.70 kb)
WilberBeast.TreeView.Demo.doc (100.70 kb)
Same as the zip version, just renamed to .doc to help with downloading.
WPF, MVVM and RaisePropertyChanged
Several times recently during development I have come across situations where specific strings are required for functionality to work effectively and yet not having the correct string will not cause any compilation or run time errors, features just won’t work. The features may be subtle enough such that it could be some time before any break in functionality is noticed and therefore could reach production code. In coding terms these are called “magic strings” and are something to be avoided at all costs.
One example of this I have come across often in MVVM WPF or Silverlight projects where the ViewModels implement the INotifyPropertyChanged interface such that when a property is changed it is common to see code such as the following.
private string _firstName; public string FirstName { get { return _firstName; } set { if (_firstName != value) { _firstName = value; RaisePropertyChanged("FirstName"); } } }
The alarm bell for me here is the hard coded name of the property “FirstName” within the RaisePropertyChanged method. In this simple example it is clear, but in more complex code it can easily get lost and also magic strings such as these won’t be picked up and adjusted in any automated refactoring of the names of the properties.
You can obtain the name of the property using the following bit of code, however it includes either ‘get_’ or ‘set_’ at the start depending upon where it is called.
MethodBase.GetCurrentMethod().Name;
Thus I’ve created the following extension method to take the property name and simply strim off the first 4 characters thus leaving you with the correct property name.
public static string GetPropertyName(this MethodBase methodBase) { return methodBase.Name.Substring(4); }
Using this extension method, as below, it means that you will be able to raise property changed events without concern that in future your property name might change.
RaisePropertyChanged(MethodBase.GetCurrentMethod().GetPropertyName());
Update: I’ve now created a Visual Studio code snippet that is used for creating field backed properties that automatically includes the “RaisePropertyChanged” call which can be found here.