Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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?

share|improve this question

1 Answer 1

up vote 2 down vote accepted

I think first of all you should get rid of looking for dataTemplates by type name. Then instead of your method you will be able to use FindResource method:

var resourceKey = new DataTemplateKey { DataType = type };
var dataTemplate = (DataTemplate)frameworkElement.FindResource(resourceKey);

Regarding your second question: I think it doesn't matter which one you will take first, as far as I remember they are mutually exclusive. Also MSDN says:

For templates, the Parent of the template eventually will be null. To get past this point and extend into the logical tree where the template is actually applied, use TemplatedParent

So taking this statement into account I would take Parent ?? TemplatedParent.

share|improve this answer
    
Well, the first suggestion is pointless, seeing as that's what I'm trying to do, and what you suggest would only add levels of complexity to my application. But thanks for part 2, as that answers one of my concerns. –  Will Jun 16 '11 at 13:56
    
Crap, I feel like a jerk now. I read too much into your suggestion get rid of looking for DataTemplates by type name, and discounted your alternative suggestion. Now, that works pretty good... in fact, much better than my current code in some respects, so this is worth accepting as the best solution. I would still like to support text keys, but for now... –  Will Jul 13 '11 at 16:55

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.