In this post, I'll show how I implemented the ContentControl
implicit data-template using the ImplicitContentTemplateBehavior
attached behavior.
But first, let's talk a bit about how WPF searches for an implicit data template, given a content, so we can mimic that behavior in Silverlight.
Having a ContentControl
with a Content
set to an instance of type Circle
, WPF looks at the ControlControl.ItemTemplate
. In case that one is missing, and there is no ItemTemplateSelector
, it changes strategy, and tries finding a Data Template by traversing upward the logical-tree, looking inside each element resource dictionary for a template which has the type of Circle
(or base classes – but not interfaces!) as the key (implicitly or explicitly set).
First, it looks at the ContentControl
resources, if not found, it goes one level up, and searches at the parent level until one is found or until it reaches the root element, then finishes by searching at the Application level resources.
So now that we have an idea, let's look at the ImplicitContentTemplateBehavior
attached behavior.
public class ImplicitContentTemplateBehavior : Behavior<ContentControl>
{
protected override void OnAttached()
{
var binding = new Binding("Content")
{
Mode = BindingMode.OneWay,
Source = AssociatedObject,
Converter = new DataTemplateConverter(AssociatedObject),
};
BindingOperations.SetBinding
(AssociatedObject, ContentControl.ContentTemplateProperty, binding);
base.OnAttached();
}
private class DataTemplateConverter : IValueConverter
{
private ContentControl _contentControl;
public DataTemplateConverter(ContentControl contentControl)
{
this._contentControl = contentControl;
}
#region IValueConverter Members
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return ImplicitDataTemplateResolver.Resolve(_contentControl);
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
}
As you can see, I've implemented the OnAttached
method of the attached behavior by creating a Binding extension to bind the ContentControl
(the associated object) Content Template property with the implicit data template, using a value converter which actually searches for the data template upward the tree.
Whenever the Content
property is changing, the converter Convert
method is called. Here, I'm using a helper class to find the data template.
Let's look at the ImplicitDataTemplateResolver
helper:
internal static class ImplicitDataTemplateResolver
{
internal static DataTemplate Resolve(ContentPresenter contentPresenter)
{
return Resolve(contentPresenter, contentPresenter.Content);
}
internal static DataTemplate Resolve(ContentControl contentControl)
{
return Resolve(contentControl, contentControl.Content);
}
private static DataTemplate Resolve(FrameworkElement contentElement, object content)
{
DataTemplate resolvedDataTemplate = null;
if (content != null)
{
resolvedDataTemplate = InternalResolve(contentElement, content.GetType().FullName);
}
return resolvedDataTemplate;
}
private static DataTemplate InternalResolve(FrameworkElement element, string contentTypeName)
{
if (element == null)
{
return TryFindDataTemplate(Application.Current.Resources, contentTypeName);
}
var dataTemplate = TryFindDataTemplate(element.Resources, contentTypeName);
if (dataTemplate == null)
{
var parent = VisualTreeHelper.GetParent(element) as FrameworkElement;
dataTemplate = InternalResolve(parent, contentTypeName);
}
return dataTemplate;
}
private static DataTemplate TryFindDataTemplate
(ResourceDictionary resourceDictionary, string contentTypeName)
{
DataTemplate dataTemplate = null;
if (resourceDictionary.Contains(contentTypeName))
{
dataTemplate = resourceDictionary[contentTypeName] as DataTemplate;
}
return dataTemplate;
}
}
The Resolve static
method, calls a Resolve
private
method with the new content. This calls the InternalResolve
with the content full type name (I'm using this as a unique key since we can't use x:Type
in Silverlight 4), which in turn, traverse upward the Visual Tree using the VisualTreeHelper
helper class. In case a Data Template was found, it returns it. If not, keep searching up until reaching the root element. Then it looks at the Application resources as last resort.
Note: For simplicity, I didn't try to find Data Template defined for base classes, but feel free to add that if missing.
Here is the code of this sample.
In my next post, I'll show how I implemented the ImplicitItemTemplateBehavior
attached behavior, which uses the same ImplicitDataTemplateResolver
helper class, but with a different approach.
Stay tuned.