I found this question on the Silverlight forum today (it might be “the other day” since I don’t know if I will even be able to publish this today) that I wanted to answer by creating a post. Why not just point towards someone else’s blog? Well…I tried that…but I couldn’t find one by doing a couple of simple Googlings (I love that new word…I will keep it and nurture it)…so I came to the conclusion that if it is that hard to find the information, it needs to made more available…on my blog…
So..what was the question? Well, the guy wanted to know how to use templates in a custom control. That sounds like a pretty simple request. There has to be lots of information available about this…well…not really apparently. In general I would recommend reflectoring it, but in this case I felt it was better to explain. So here we go…
Let’s start off by creating a new control. Any control will do…but it should be using templates. So I created a control that had 2 properties. One DependencyProperty of type IEnumerable called ItemsSource and one regular property of type DataTemplate called ItemTemplate.
It looks like this
public class TemplatedControl : Control
{
public static DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(TemplatedControl),
new PropertyMetadata(ItemsSourceChanged));
public TemplatedControl()
{
DefaultStyleKey = typeof(TemplatedControl);
}
private static void ItemsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
TemplatedControl tc = sender as TemplatedControl;
if (tc == null)
return;
tc.OnItemsSourceChanged(e);
}
protected virtual void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
{
ClearItems();
}
if (e.NewValue != null)
{
BindItems((IEnumerable)e.NewValue);
}
}
public DataTemplate ItemTemplate
{
get;
set;
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
}
As you probably figured, there is more code…that will be added soon. Hold on… But first we need a default template. So I added a generic.xaml file and added a simple template in it
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Controls">
<Style TargetType="local:TemplatedControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TemplatedControl">
<StackPanel x:Name="Panel" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
As you can see I decided to use a StackPanel to hold my values. This could be any control inheriting from Panel and thus supporting children. When the default template is created, it is time to handle it in the control. Let’s start by overriding the OnApplyTemplate method
Panel _pnl;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_pnl = this.GetTemplateChild("Panel") as Panel;
if (ItemsSource != null)
BindItems(ItemsSource);
}
The first step is to get hold of my panel and store it for future use. The next step is to bind the items if they have been set. These will often be set before ApplyTemplate is called, so it is important to take care of it here, as well as in the callbacks for the propertychanged event.
Right now my control is missing two methods before I can call it complete. BindItems and ClearItems. In ClearItems I basically just clear the Panel’s Children collection
private void ClearItems()
{
if (_pnl == null)
return;
_pnl.Children.Clear();
}
But since the Panel is part of the template, that can be changed by the user, it is important not to just assume it is there…
The BindItems control is actually not much more complicated. Check if the Panel is there and then start looping through the items. For each item, load up the template content and set the DataContext before adding it to the Panel.
private void BindItems(IEnumerable items)
{
if (_pnl == null)
return;
foreach (var item in items)
{
FrameworkElement obj = ItemTemplate.LoadContent() as FrameworkElement;
obj.DataContext = item;
_pnl.Children.Add(obj);
}
}
That’s it. That’s how you use DataTemplates in your custom controls. I guess that the only thing left to show is how to use it in Xaml using a very clever and complex DataTemplate…or not!
<ctrls:TemplatedControl x:Name="MyControl">
<ctrls:TemplatedControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding}" />
</Grid>
</DataTemplate>
</ctrls:TemplatedControl.ItemTemplate>
</ctrls:TemplatedControl>
Cheers! I hope you got the information needed to use DataTemplates in your future templated, databound controls. And as always, don’t hesitate to ask questions if there is anything you wonder.
BTW, I guess I actually saw the forum question today…
Thanks to Brulio Diez I now have some source code available for download. Braulio went through the trouble of taking my code, commented it and packaged it up for anyone interested. Thank you Braulio!
Download code (72.86 kb)