Condividi tramite


On ItemTemplate, ContentTemplate, DataTemplate

A recent forum post asked how one could build out ItemsControl-like functionality in a custom control.

Let me start from the basics.

ContentPresenter presents content. If you give it a UIElement (Grid, Button, Border) it will just display it. Easy enough. If you give it "data" (Customer, Order, Int32) it will display it using a DataTemplate.

DataTemplate is to data as ControlTemplate is to Control. It lets one define a tree of visuals that represent a given DataContext.

Robby and I chatted about this just over a year ago.

ContentPresenter is the "thing" that does the work to realize the DataTemplate. It has the magic.

One can give ContentPresenter a DataTemplate in two ways:

  1. Set the ContentTemplate property. This is typeof(DataTemplate)
  2. Set the ContentTemplateSelector. This is typeof(DataTemplateSelector)

The selector is used if the ContentTemplate is null. In the end, it just returns a DataTemplate.

With me so far?

Now ContentPresenter has no "chrome" to speak of. If you want to show content with some interactive chrome, use a ContentControl. This way you can show some non-data state (like selection, focus) and have non-data behavior (like checking or clicking). This is what Button, CheckBox, and ListBoxItem do.

All of these controls do very little. The own some purpose-specific state and rely on a ContentPresenter in their control template to do the work of displaying content. ContentControl also has a ContentTemplate property and ContentTemplateSelector property (as well as, you guessed it, a Content property). These are never really used directly by the Control, they are simply used to alias values down to the templated ContentPresenter.

Moving to ItemsControl (IC), we have a similar story.

IC has two analogous properties:

  1. ItemTemplate [typeof(DataTemplate)]
  2. ItemTemplateSelector [typeof(DataTemplateSelector)]

Now IC does a very specific trick. It takes data items from a list, creates wrappers (or containers) for each data item, and places them in a specified panel.

GetContainerForItemOverride is a virtual method on IC that is used to generate containers--ListBox creates ListBoxItem, TreeView creates TreeViewItem, etc.

PrepareContainerForItemOverride is another virtual method on IC. This is the method that marries the generated container to the corresponding data item. In the process, it sets up the container with the right properties from the parent IC. It sets the ContentTemplate of the container to be the ItemTemplate of the IC. Same with ItemTemplateSelector. When the container is a ContentControl, these are once again proxied to an underlying ContentPresenter.

The question on the forum asks how this can be accomplished in a custom control. Well, if one doesn't want to use IC, one can accomplish the same set of things. Create code to generate either a ContentPresenter or ContentControl for each data item. Expose Template/TemplateSelector properties on your parent control. When you generate child items, make sure you alias the properties from parent to child. That's about it.

I do this trick in the Graph control as part of the Bag-o-tricks.

I know that the ItemTemplate, ContentTemplate, DataTemplate naming thing can be confusing. Remember, at the end of the day, it all boils down to a ContentPresenter + Template to display data is a cool way.

Hope this answers the question and sheds some light on the workings of our more complicated controls.

Happy hacking!