Using DataTemplates in custom controls

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)

Comments (10) -

Great post !, thanks a lot.

Just one enhancemente, posting the sample solution would help a lot as well.

thanks
  Braulio

Just a silly thing, copy pasting code from you site to Visual Studio does not work quite well (just paste all in one line).

Not sure if you already were aware of that.

Cheers
  Braulio

I know. Sorry about tthat. The code snippet plug-in I use for Live Writer doesn't handle that part very well. Some day I will sit down and solve this.
I will also try to get the code up as a download. I have just been a bit too busy...

Yes that's the bad thing of working in this business... you make marvellous things just for fun Wink.

No prob, I have coded your sample (quite good and simple), I'm planning to go adding your post content to the code. If you want once it's finished I can send you the solution.

I want to use this custom controls + datatemplate for things like outlook calendar controls (not linear display, just following some special rules), if I manage to get something (a bit busy as well these times :-() will ping you.

Thanks a lot for your help, great work.
  Braulio

Hi!
Thank you for the post.
I think you can do the same by writing next:

<ContentPresenter x:Name='_ContentPresenter'/>

and then

this._ContentPresenter.ContentTemplate = ItemTemplate;

Oh, I missed that you're using it for a list of items..

Muhammad shabbir 1/25/2012 5:45:53 PM

Hi ,its gud sample ,lets suppose we want to add the textchanged event to textbox how we can add an event handler to textbox.
wud be greateful if any body could answer.
with regards shabbir

Muhammad shabbir 1/25/2012 5:48:10 PM


or
how we can add click event on the datatemplate .
wud be greateful if any body could answer.
with regards shabbir

Hi Shabir!

Well, handler for events on controls in the data template should not be in the control that uses the data template. It should not depend on anything in the template. If you need that, you should be looking at a control template instead. If you need to handle an event in a data template, that should go into the code for the control that defines the data template...

// Chris

Great article! Thanks.

Comments are closed