image flash

WPF Data Templates

September 10th, 2008

This is part one in a series of templates that will take a person through the process of creating a simple WPF application. Part one is going to demonstrate how to use data templates to define the way your data objects are represented in your application. For the purposes of these tutorials, we are using stub data provider classes and methods to populate our data.

Goal:

Let’s suppose that you are creating a system that will allow you to browse through a collection of customers. Each customer in turn can have a collection of orders, and those orders have a collection of items in them. Ultimately, we will be building a basic system where you can browse the customers and select the customer, order, or its items to be viewed and managed in another column in the layout.

In this part of the tutorial, we are going to create the initial tree view and populate it with some customers provided by our stub data provider. The working solution can be downloaded here.

Getting Started

You can download the project used for this article here: TreeViewTutorialPt1.zip

First thing you will want to do after creating your solution and adding a WPF application project to it, is set up a few files for use. We added a directory to our project called Classes, and one called Controls. It should be fairly straightforward what each of those directories is going to be used for. We also added a Resource dictionary to the project called Resources.xaml.

Using Your NameSpaces

In order to use your classes and controls, you will need to register xml namespaces at the top of your controls and windows. See Window1.xaml for an example:

xmlns:ctrls="clr-namespace:ThoughtlabTutorial.Controls"

Putting this at the top makes it so you can use an xml namespace, and it ties that to any defined controls in that namespace. By starting out with our directory Controls in the solution, any UserControl we add to that folder will be set up in the namespace ThoughtlabTutorial.Controls.

So in our solution we can do the following inside our Window:

That will now use the control that we created in the Controls directory. Namespaces can also be used for referencing classes that XAML will need to be aware of. For example, if we look at the Resources.xaml file we will see it reference our own classes namespace:

xmlns:TutorialClasses="clr-
namespace:ThoughtlabTutorial.Classes"

This allows us to set the DataType that our DataTemplates will refer to:

<DATATEMPLATE DataType="{x:Type TutorialClasses:Item}"
x:Key="ItemTemplate">

Creating Data Templates

To really start using your data templates, you will want to have the classes you are going to be templating defined. I am not going to spend any time on that because the example code is very simple and quite self explanatory. Obviously it doesn’t make much sense to try and build a template that binds to class properties if you don’t even know what those properties are though. So go ahead and look at the Classes.cs file to see the different types of classes we will be using.

There are two types of DataTemplates that we will want to use.

  • DataTemplate: A simple template without any notion of having a collection of children. This is the template we use for our leaf nodes in the Hierarchical template.
  • HierarchicalDataTemplate: A more robust template that supports having children.

NOTE: The order you define these templates in your resource dictionary is important. The XAML parser will throw exceptions at run time if you reference a template that is not already parsed. So you effectively end up ordering the templates from the leaf node up to the root node.

DataTemplate

So let’s look at our Data Template node again

<DATATEMPLATE DataType="{x:Type TutorialClasses:Item}"
x:Key="ItemTemplate">

Let’s look at both these attributes really quickly:

  • x:Key: This defines the name this resource item is looked up by
  • DataType: This is where we define the class of the objects that will use this template. Notice how we are using the xml name space defined at the top of our XAML

Now you can define what controls you want to use to represent your classes members and properties. In our ItemTemplate you will see the following for example:

<TEXTBLOCK Text="{Binding Path=Name}">

Because we have defined the class that the template uses, we can then bind to its members quite simply.

HerarchicalDataTemplate

The concept is exactly the same with a hierarchical data template, but we have a few more attributes as you would expect.

<HIERARCHICALDATATEMPLATE DataType="{x:Type
TutorialClasses:Order}" x:Key="OrderTemplate"
ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding Path=Items}">

Once again we have the key, and the data type, but you will also notice the two new attributes. ItemsSource tells this template where to look to find a collection of child elements. The ItemTemplate then tells it where to find the template to use for those children. Notice that the value the ItemTemplate refers to is the same as the x:Key value was for the template we defined for our Item objects. Anything you assign with syntax like “{StaticResource MyResourceName}” is going to look in the resource dictionary for that XAML window/control/page etc and apply the resource item with the matching key.

Setting Up Our TreeView

So we have some data templates now, but they don’t do us any good unless we can do two things.

  1. Assign the template to the TreeView
  2. Populate the TreeView with data

To see how we do this, you will want to look at the TreeViewControl.xaml and it’s associated code-behind file.

XAML

The XAML is quite simple to pull this off. First we need to give the user control a name that the XAML parser can refer too. This is done similar to the way we create namespaces at the top.

x:Name="ctrlMyTreeView"

This is important because we can’t do a successful bind to the control’s properties unless the control has a name for the parser to use. The other option is to set the data context for the control to a dependency property. Explicitly naming the element gives you more freedom and flexibility though. This makes the next bit of XAML make a lot more sense:

<TREEVIEW ItemTemplate="{StaticResource
CustomerTemplate}" ItemsSource="{Binding
ElementName=ctrlMyTreeView,Path=MyCustomers}">

So here you can see that we tell the parser what element to look too in order to find the property MyCustomers to get its data from. Then you see the same resource lookup syntax to tell the parser where to find the template for the items in this tree view. Notice that the hierarchical template we defined controls the template used at each level. All we have to define is the root template, and that in turn informs the parser of the template to use for its children etc.

Code

The code for this requires you to set up what is called a dependency property. The DependencyProperty code enables the XAML parser to connect to the property defined in the code for the control/window/page etc. It also takes care of updates to the interface when the value of that property is changed. The easiest way to do this with Visual Studio is to type “propdp” and hit tab. That creates the snippet. You can change names as you work through it and hit tab twice to move to the next one you should change. The values you are expected to personalize are highlighted in green.

Our dependency property looked like this:

public ObservableCollection MyCustomers
{ get { return GetValue(MyCustomersProperty)
as ObservableCollection; }
set { SetValue(MyCustomersProperty, value); } }
public static readonly DependencyProperty
MyCustomersProperty =
DependencyProperty.Register("MyCustomers",
typeof(ObservableCollection) ,
typeof(TreeViewControl) ,
new UIPropertyMetadata(null));

You will want to look out for a few things when setting up your property:

  • For collections you need to use the ObservableCollection type. This requires System.Collections.ObjectModel
  • Make sure that your 3rd parameter in the Register method is the class of your user control. In copy-paste moments that is an easy thing to overlook. In our example, this parameter is on line 32. Notice that it is the type of the actual class the code is in
  • The last value is basically a default value. Usually you pass in null, unless your class is a reference object like an int

Once you have set up your dependency property, you will want to load your data. We recommend that you always attach a loaded event in your constructor and then do everything else in that loaded event. This keeps data loads and other things from running unless the XAML successfully parses for the application.

public TreeViewControl()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MyControlLoaded);
}
private void MyControlLoaded(object Sender,
RoutedEventArgs e)
{
MyCustomers = StubDataProvider.loadCustomers();
}

Wrapping Up

So this should give you enough information to get started. The example project is anything but pretty, but it does demonstrate how this allows you to set up unique functionality based on the type of object you are dealing with. In future tutorials we will talk about hooking up your interactions and setting up your own delegated events. We will also discuss an unusual issue dealing with Focus, and how to get around that.

The last part of the tutorial will discuss binding the object you select to other controls, setting up data transformations for currency and dates, as well as other ways to improve performance beyond what the TreeView control can provide.