I'm trying to emulate the default behavior of an ItemsControl in a ContentControl--Bind Content to an object property and use the correct DataTemplate based on that object's type.
I've tried to Reflector dotPeek at the implementation of the ItemsControl to see how it works, but I've reached a dead end. Consequently, I've come up with this custom DataTemplateSelector. Here's the code, after which I'll describe why it sucks.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Markup;
namespace TemplateSelectorAndInheritance
{
/// <summary>
/// A <see cref="DataTemplateSelector"/> that finds <see cref="DataTemplate">
/// DataTemplates</see> by the bound object's <see cref="Type"/>.
/// </summary>
/// <remarks>This selector supports keys defined by both the
/// <see cref="TypeExtension"/> and the type name specified by namespace
/// (xmlns).</remarks>
public sealed class TypeBasedDataTemplateSelector : DataTemplateSelector
{
/// <summary>
/// When overridden in a derived class, returns a
/// <see cref="T:System.Windows.DataTemplate"/> based on custom logic.
/// </summary>
/// <param name="item">The data object for which to select the template.</param>
/// <param name="container">The data-bound object.</param>
/// <returns>
/// Returns a <see cref="T:System.Windows.DataTemplate"/> or null. The default
/// value is null.</returns>
public override DataTemplate SelectTemplate(
object item, DependencyObject container)
{
if (item == null)
return null;
if (container == null)
throw new ArgumentNullException("container");
return FindFirstDataTemplate(
item.GetType(),
container as FrameworkElement);
}
/// <summary>
/// Recursively searches up the visual tree searching for an applicable template.
/// </summary>
/// <param name="type">The <see cref="Type"/> of the template</param>
/// <param name="frameworkElement"><see cref="FrameworkElement"/></param>
/// <returns><see cref="DataTemplate"/> if found, <c>null</c> otherwise.</returns>
private DataTemplate FindFirstDataTemplate(
Type type,
FrameworkElement frameworkElement)
{
if (frameworkElement == null)
return null;
var key = frameworkElement.Resources.Keys.OfType<DataTemplateKey>()
.FirstOrDefault(x =>
{
// there be hacks here
var targetType = x.DataType as Type;
var targetTypeName = x.DataType as string;
// this one is very bad, since it doesn't take into account namespaces
if (targetTypeName != null && targetTypeName.EndsWith(type.Name))
return true;
if (targetType != null && targetType == type)
return true;
return false;
});
if (key != null)
return frameworkElement.Resources[key] as DataTemplate;
// here's another hack--I'm picking the TP first, but I'm not sure
// if this is best all the time?
var parent = frameworkElement.TemplatedParent as FrameworkElement ??
frameworkElement.Parent as FrameworkElement;
return FindFirstDataTemplate(type, parent);
}
}
}
This works, but is very fragile and kludgy. I'm hoping to get help on the following points:
First: If you don't use the {x:Type} markup extension, this selector may fail. I can't figure out, within the limited context of the SelectTemplate method, how to find the defined namespaces. For example, if you were to do the following
<DataTemplate xmlns:t="clr-namespace:Fubar" DataType="t:Derp"
then the DataTemplateKey's value is a string: "t:Derp". I can't figure out how to find out what namespace that t: represents within the context of the SelectTemplate method.
Second: My method of searching up the physical/logical tree looking for resources sucks and makes me unhappy. There has got to be a better way to search for resources!
Third: As I search up my parents, I've almost arbitrarily decided that I'll select a TemplatedParent before a Parent, but I'm not even sure that's the best thing to do.
Whadyathink?