|
Data templates are excellent. Using a DataTemplateSelector is even more useful,
unfortunately, if you ever really want to use one, you face one major problem,
finding out how to use them properly because all the MSDN documentation for the DataTemplateSelector and its usage absolutely
suck!
The following code shows the method used to find a template selector by finding
the main window of the application and searching within its resources:
public class TaskListDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
if (item != null && item is Task)
{
Task taskitem = item as Task;
Window window = Application.Current.MainWindow;
if (taskitem.Priority == 1)
return
window.FindResource("importantTaskTemplate") as DataTemplate;
else
return
window.FindResource("myTaskTemplate") as DataTemplate;
}
return null;
}
}
This technique however, has a flaw in it inasmuch as the FindResource method
actually searches in the wrong direction for this code to be really useful.
FindResource walks from a node buried deep within the logical tree and searches
backwards towards the root. With the MSDN sample, if the template isn't at the
level of the main window the resource will not be found.
Now, the advantage of this behaviour may not be abundantly clear at first glance
however, we could imagine that data we bind to in our WPF or Silverlight UI
might be represented differently in different objects, say in a TreeView and in
a ListView showing two different aspects of the data. Imagine then, several
DataTemplates, one for each view, being selected for the UI element using a
common name but stored in different resource dictionaries. In this way we can
use the simplest data binding scenario and allow the position of the resource in
the UI markup to determine the look of the visual.
Where does the promise of reflection fall into all of this you may be
asking? Well, when a DataTemplateSelector is needed, a delegate is called to
perform the selection. This delegate takes as its parameters the object that is
in the binding context and a DependencyObject which is the container for the
bound object. This container will be of a different type depending on the type
of UI element used. Because we may be using several templates in several views
we cannot be certain of which type of object the container will be so, a little
bit of reflection will allow us to find the desired resource no matter where the
selector is used.
In the example, I have created a very simple object and the template selection
will take place according to the type of the object. Of course, in your own
implemetations you may be interested in other properties of the data object but
for now, this will suffice. The data objects form a tree-like structure which is
ideal for display in a TreeView. When we select a node in the tree view, the
data is placed into the data context of a list view and a different aspect of
the data is seen. The only remotely complicated thing about the data itself is a
little routine which creates the initial tree of data objects. Here is the code
for those simple data objects:
public class
BasicThing
{
public BindingList<BasicThing> Things {
get; protected
set; }
public BasicThing()
{
Things = new
BindingList<BasicThing>();
}
static Random
r = null;
public static
BindingList<BasicThing>
GenerateThings(int level)
{
if (r == null)
r = new
Random();
BindingList<BasicThing> blt = new
BindingList<BasicThing>();
int nl = level - 1;
if(nl>0)
{
int l=(r.Next(10) + 2);
for (int
n = 0; n < l; n++)
{
BasicThing bt=null;
switch (r.Next(3))
{
case 0:
bt = new
Thing1();
break;
case 1:
bt = new
Thing2();
break;
case 2:
bt = new
Thing3();
break;
default:
throw new
Exception();
}
bt.Things = GenerateThings(nl);
blt.Add(bt);
}
}
return blt;
}
}
public class
Thing1 :
BasicThing
{
}
public class
Thing2 :
BasicThing
{
}
public class
Thing3 :
BasicThing
{
}
Now that the data objects exist, we can design some data templates for the
various visual appearances of the objects as they are seen in the tree view.
Here's some XAML for that:
<TreeView.Resources>
<HierarchicalDataTemplate x:Key="thing1DataTemplate"
ItemTemplateSelector="{StaticResource
theSelector}" ItemsSource="{Binding Things}">
<Grid Margin="4,4,4,4">
<Ellipse Width="15"
Height="15" Fill="Red"/>
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="thing2DataTemplate"
ItemTemplateSelector="{StaticResource
theSelector}" ItemsSource="{Binding Things}">
<Grid Margin="4,4,4,4">
<Rectangle Width="15"
Height="15" Fill="Blue"/>
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="thing3DataTemplate"
ItemTemplateSelector="{StaticResource
theSelector}" ItemsSource="{Binding Things}">
<Grid Margin="4,4,4,4">
<Image Width="15"
Height="15" Source="Bitmap1.bmp"/>
</Grid>
</HierarchicalDataTemplate>
</TreeView.Resources>
You can see that these are hierarchical data templates which enable the tree
view to display the full structure of the data. You can also see that the
templates all use the same template selector for their sub-items and that they
are named with keys as thing1DataTemplate, thing2DataTemplate and
thing3DataTemplate. Make a note of this for later.
The TreeView itself is declared in the following code and shows the simplicity
of the component setup:
<TreeView Grid.Column="0"
x:Name="treeView1"
ItemTemplateSelector="{StaticResource
theSelector}" TreeViewItem.Selected="treeView1_Selected">
<TreeView.Resources>
…The templates you’ve already seen…
</TreeView.Resources>
</TreeView>
So, you see here that the template selector is used by the TreeView to select
the template for the root node. The same selector declared in the templates
themselves will serve for the sub nodes.
Now we come to the templates used in a ListView. In the next bit of XAML you
will see once again that the resources holding the templates are stored by the
graphical object itself. The ListView declares the same DataTemplateSelector and
the templates themselves all use the same key names as those used in the
TreeView. You will realise now that this is the advantage offered by
FindResource because the container object for the ListView will be able to
search back to the resources that it holds and the container for the TreeView
will find its own local resources such that the templates for the different
visual styles are applied to the different UI controls. Here's the ListView
XAML:
<ListView Grid.Column="1"
x:Name="listView1"
ItemsSource="{Binding
Things}" ItemTemplateSelector="{StaticResource theSelector}">
<ListView.Resources>
<DataTemplate x:Key="thing1DataTemplate">
<Grid Margin="6,6,6,6">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
<Ellipse Width="30"
Height="30" Fill="Red"/>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="thing2DataTemplate">
<Grid Margin="6,6,6,6">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
<Rectangle Width="30"
Height="30" Fill="Blue"/>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="thing3DataTemplate">
<Grid Margin="6,6,6,6">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
<Image Width="30"
Height="30" Source="Bitmap1.bmp"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.Resources>
</ListView>
Now the famous DataTemplateSelector based object itself. The code for this is
once again very simple and is shown below:
public class
ThingTemplateSelector :
DataTemplateSelector
{
public override
DataTemplate SelectTemplate(object item,
DependencyObject container)
{
MethodInfo mi =
container.GetType().GetMethod("FindResource")
as MethodInfo;
if (mi != null)
{
switch (item.ToString())
{
case
"ReflectedTemplateSelection.Thing1":
return mi.Invoke(container,
new object[] {
"thing1DataTemplate" })
as DataTemplate;
case
"ReflectedTemplateSelection.Thing2":
return mi.Invoke(container, new
object[] {
"thing2DataTemplate" }) as
DataTemplate;
case
"ReflectedTemplateSelection.Thing3":
return mi.Invoke(container, new
object[] {
"thing3DataTemplate" }) as
DataTemplate;
}
}
return null;
}
}
The application when running shows the different templates in the TreeView and
ListView:
The complete ZIP file containing the demo code for this article can be downloaded from this link.
|