Search code examples
c#wpfdatatemplatecode-behind

How can you programatically retrieve the DataTemplate used for a specific element in the UI hierarchy?


We need to determine, in code, what template would automatically be applied to a bound element, given a specific data type and that element.

We are not looking for a DataTemplateSelector as that's used to tell the UI which template to use for a given object based on custom logic. We're instead asking the UI which template will it use for a given data type and UI element.

In other words, we're looking for whatever WPF would apply based on the templates defined in the resources section of a window, which can be overridden by the resources of a control on that window, which can be overridden by explicitly setting the DataTemplate or providing a DataTemplateSelector directly on that element.

Also, we tried the default implementation of SelectTemplate but that returns null, so we can't go that route either.

A test would be to ask an element with no data templates or selectors defined anywhere in the UI 'How would you display this value?' and hopefully it will return a DataTemplate containing the definition for a TextBlock with the text property set to the ToString method on that object, which is what's displayed by default when nothing else is defined.


Solution

  • Thomas Levesque's untested solution didn't quite work for me but provided a great starting point. In our case the "container" argument is not always in the visual tree so first we walk up the logical tree until we find a visual. That, combined with MarqueIV's excellent suggestion, leads to a fairly simple solution.

    The following code is working for me in production. Your mileage may vary. :)

    public static DataTemplate FindTemplateForType(Type dataType, DependencyObject container)
    {
        var frameworkElement = container as FrameworkElement;
        if (frameworkElement != null)
        {
            var key = new DataTemplateKey(dataType);
            var template = frameworkElement.TryFindResource(key) as DataTemplate;
            if (template != null)
                return template;
        }
    
        if (!(container is Visual || container is Visual3D))
        {
            container = FindClosestVisualParent(container);
            return FindTemplateForType(dataType, container);
        }
        else
        {
            var parent = VisualTreeHelper.GetParent(container);
            if (parent != null)
                return FindTemplateForType(dataType, parent);
            else
                return FindTemplateForType(dataType, Application.Current.Windows[0]);
        }
    }
    
    public static DependencyObject FindClosestVisualParent(DependencyObject initial)
    {
        DependencyObject current = initial;
        bool found = false;
    
        while (!found)
        {
            if (current is Visual || current is Visual3D)
            {
                found = true;
            }
            else
            {
                current = LogicalTreeHelper.GetParent(current);
            }
        }
    
        return current;
    }