If you’ve written a custom control in either WPF or Silverlight then you’re no doubt familiar with the OnApplyTemplate() method you override in order to initialise your child controls via the GetTemplateChild() method. A typical (and in this case, fairly useless control) might look something like this:
[TemplatePart(Name = "PART_ContentTextBox", Type = typeof(TextBox))]
[TemplatePart(Name = "PART_LabelTextBlock", Type = typeof(TextBlock))]
public class MyControl : Control
{
private TextBox contentTextBox;
private TextBlock labelTextBlock;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.contentTextBox = this.GetTemplateChild("PART_ContentTextBox") as TextBox;
if (this.contentTextBox == null)
{
throw new Exception("Couldn't find PART_ContentTextBox");
}
this.labelTextBlock = this.GetTemplateChild("PART_LabelTextBlock") as TextBlock;
if (this.labelTextBlock == null)
{
throw new Exception("Couldn't find PART_LabelTextBlock");
}
this.contentTextBox.Text = "Contents";
this.labelTextBlock.Text = "Label";
}
}
Now whilst there’s nothing wrong with that, there’s some code duplication going on and for each child control I need 5 lines of code. This is gonna get kinda messy for anything more than a simple control. The major cause of the code bloat is GetTemplateChild() which returns you a DependencyObject requiring the need to cast, which in turn requires null checking. This code would look so much nicer if only GetTemplateChild() were a generic method… so let’s refactor our code so that it is.
[TemplatePart(Name = "PART_ContentTextBox", Type = typeof(TextBox))]
[TemplatePart(Name = "PART_LabelTextBlock", Type = typeof(TextBlock))]
public class MyControl : Control
{
private TextBox contentTextBox;
private TextBlock labelTextBlock;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.contentTextBox = this.GetTemplateChild<TextBox>("PART_ContentTextBox");
this.labelTextBlock = this.GetTemplateChild<TextBlock>("PART_LabelTextBlock");
this.contentTextBox.Text = "Contents";
this.labelTextBlock.Text = "Label";
}
private T GetTemplateChild<T>(string childName) where T : DependencyObject
{
T childControl = this.GetTemplateChild(childName) as T;
if (childControl == null)
{
throw new Exception(string.Format("Couldn't find {0}", childName));
}
return childControl;
}
}
Now that’s pretty sweet – our OnApplyTemplate() method is easier to read, more focused and requires less lines of code. We’ve also centralised our error checking, removing the duplication. Whilst this is cool, if we want to use this generic method in more than one control [without copying/pasting] it needs to live elsewhere.
The major hurdle to overcome in this refactor is that GetTemplateChild() is a protected method defined in Control (i.e. only available to inheritors). As such, an extension/static method isn’t going to give you access. After a bit of a 3-way messenger convo with Rich and Matt there were 3 options on the table, none of which were without flaw: create a base class to contain our generic GetTemplateChild<T>() method, use reflection to cheat, or implement an interface on which we can implement an extension method. All have problems – but I’ve chosen to go with the interface/extension method implementation, which looks a little something like this:
internal interface IGetTemplateChild
{
DependencyObject GetTemplateChildHelper(string childName);
}
internal static class ExtensionMethods
{
internal static T GetTemplateChild<T>(this IGetTemplateChild control, string childName)
where T : DependencyObject
{
T childControl = control.GetTemplateChildHelper(childName) as T;
if (childControl == null)
{
throw new Exception(string.Format("Couldn't find {0}", childName));
}
return childControl;
}
}
[TemplatePart(Name = "PART_ContentTextBox", Type = typeof(TextBox))]
[TemplatePart(Name = "PART_LabelTextBlock", Type = typeof(TextBlock))]
public class MyControl : Control, IGetTemplateChild
{
private TextBox contentTextBox;
private TextBlock labelTextBlock;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.contentTextBox = this.GetTemplateChild<TextBox>("PART_ContentTextBox");
this.labelTextBlock = this.GetTemplateChild<TextBlock>("PART_LabelTextBlock");
this.contentTextBox.Text = "Contents";
this.labelTextBlock.Text = "Label";
}
public DependencyObject GetTemplateChildHelper(string childName)
{
return this.GetTemplateChild(childName);
}
}
For me it feels the lightest, most extensible and least intrusive. The obvious downside is that we’ve broken encapsulation by essentially exposing the protected GetTemplateChild() method. That said, it’s still a fairly elegant implementation and as all the options have their dark side, I think I’ll sleep tonight. I’d certainly be interested in any other viewpoints or ideas – is there a flawless 4th way?