In this article, I’m going to explain how to modify the custom DataTemplateSelector described by WindowsPhoneGeek to achieve these additional benefits:
- Move the DataTemplate definitions from the ListBox to the page or app resources
- See the result at design time.
Before modifying the source code, let’s see how a DataTemplateSelector works. On Silverlight for Windows Phone, we don’t have the DataTemplateSelector class available in WPF so we need to create something from scratch. Suppose that:
- We have a ListBox
- We have an ObservableCollection of Items as the ListBox source
- Each item can be of type A, B or C, as stated in one of item’s properties.
- For each type we have defined a DataTemplate
We want to automagically show each item in the list with the associated Template, like shown in the figure:
To achieve this result, we have to set the ItemTemplate property of the ListBox to something that will change dynamically at run time, depending on the discriminating property value of each item of the ObservableCollection, something like this:
So the “SomeMagicElement” should be something that:
- Has an event that will be called each time an item is rendered;
- Has a Content property set to the item to be rendered;
- Has a DataTemplate property that we can dynamically set with our custom logic every time the event is called.
This is perfectly suited for the ContentControl class, looking at his definition:
Thanks to the UI composition nature of XAML, we can place this component as the DataTemplate property of the ListBox’s ItemTemplate.
The only missing point is number three, but we can add it with a derived class, named “DataTemplateSelector”, that has a “SelectTemplate” method called by the “OnContentChanged” overridden method, where we can set the “ContentTemplate property”:
However, this is not be a good solution, because we may have more than one ListBox in our application with different type of items and data templates, with different custom logics. A better approach is to create an abstract class with a virtual method, so that we can create any number of different custom template selectors, derived from the abstract class:
Here is a CustomTemplateSelector, based on the above example:
You can see that is based on the abstract class DataTemplateSelector and that we have overridden the SelectTemplate method. Then you can ask yourself why we added the three DataTemplate properties, and you are right. The answer is on the clever way that WindowsPhoneGeek proposed in his article of passing the possible Data Templates from the XAML where we define the ListBox to the CustomTemplateSelector instance. Look at the XAML code:
Inside the DataTemplate property of ItemTemplate we set (i.e. we create an instance) of our CustomTemplateSelector and we set his properties DataTemplateA, DataTemplateB and DataTemplateC, so the CustomTemplateSelector can use those property values to return the DataTemplate associated to each item type. Et voilà, we have explained exactly what WindowsPhoneGeek proposed in his article, and everything works perfectly. However, we face two problems:
- We cannot see the list at design time so we have to run the app to check the DataTemplates and this can be very time consuming and frustrating design experience.
- The DataTemplates are buried inside the ListBox definition. If we have more than a ListBox in our application that use the same DataTemplates, we have duplicated code, and this isn’t good at all.
Therefore, here are my proposed changes, to solve both problems.
First of all, let’s use a data context container (a ViewModel class, in MVVM jargon…):
Because we don’t want to set the page’s data context at runtime, we cannot use the classic method in the page’s code behind:
Instead, we need to define the page’s data context on XAML. To do so, we need to define a “local” xml namespace and the DataContext property:
That way, the MainViewModel class is instantiated also at design time. However, this is not enough, because the MainViewModel class constructor will initialize the observable collection, but without any data.
To add some fake data to the observable collection we can use a known trick: test the static property “IsInDesignModeProperty” of the static class “DesignerProperties” and if true call a helper method:
This solve our first issue, because now we can see it at design time:
To solve the second issue, we need to change the way the custom template selector get the DataTemplates that he needs. Because if we move the template definitions outside the ListBox, to the page or app resources, we lose the way of setting the custom template selector’s DataTemplate properties. The solution is to get rid of them altogether in favor of a helper method that, given the name of the resource, will try to find it in the app resources dictionary or in the page resources dictionary. Nevertheless, this pose another couple of problems:
- We need a mechanism to cache the found templates, to avoid repetition. This is an important optimization point because the custom template selector is called each time an item is rendered.
- Looking at the app resources is easy, but getting the page resources from the custom template selector is much harder.
The first point is easy, as we can add a dictionary to the abstract class that will contains all the needed data templates:
The second point is solved first looking at the app resources dictionary, and then using the “GetParent” method of the “VisualTreeHelper” static class, to find the “page” container and its resources dictionary:
At the end, we have the data template on the resources of the page:
Here is the complete code of the DataTemplateSelector with dictionary and helper method:
Finally the custom template selector, now much cleaner as he contains only the custom logic:
Here you can download the complete solution: http://sdrv.ms/1lGje5J
That’s all folks!