4
votes

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.

3
What about DataTemplateSelector [msdn.microsoft.com/en-us/library/…pufferfish
Isn't the selector where you determine what to return? I want to use what WPF would otherwise resolve to.Mark A. Donohoe

3 Answers

1
votes

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;
}
0
votes

I guess you could try to manually reproduce the logic WPF is using to find the appropriate DataTemplate, by walking up the visual tree and looking for a resource with the appropriate key. Here's a possible implementation (untested):

static DataTemplate FindTemplateForType(Type dataType, DependencyObject container)
{
    var frameworkElement = container as FrameworkElement;
    if (frameworkElement != null)
    {
        var template = FindTemplateForType(dataType, frameworkElement.Resources);
        if (template != null)
            return template;
    }

    var parent = VisualTreeHelper.GetParent(container);
    if (parent != null)
        return FindTemplateForType(dataType, parent);
    else
        return FindTemplateForType(dataType, Application.Current.Resources);
}

static DataTemplate FindTemplateForType(Type dataType, ResourceDictionary resources)
{
    var entries =
        from DictionaryEntry e in resources
        where e.Key is Type && e.Value is DataTemplate
        let type = (Type)e.Key
        let template = (DataTemplate)e.Value
        where dataType.IsAssignableFrom(type)
        select template;

    var template = entries.FirstOrDefault();
    if (template != null)
        return template;

    foreach(var mergedDic in resources.MergedDictionaries)
    {
        template = FindTemplateForType(dataType, mergedDic);
        if (template != null)
            return template;        
    }

    return null;
}
0
votes

i used the same way as karfus , and added search datattemplate to baseType in case there is relevant datatemplate to Type.Basetype

<Extension>
Public Function GetDatatemplateForType(container As DependencyObject, dataType As Type) As DataTemplate
    Dim dTemplate As DataTemplate = Nothing
    Dim currentType As Type = dataType
    Do While dTemplate Is Nothing And currentType IsNot Nothing
        dTemplate = DataTemplateForType(container, currentType)
        currentType = currentType.BaseType
    Loop
    Return dTemplate
End Function

Private Function DataTemplateForType(Container As DependencyObject, dataType As Type) As DataTemplate
    Dim resTemplate As DataTemplate = Nothing
    Dim dKey As DataTemplateKey = New DataTemplateKey(dataType)
    Dim fm As FrameworkElement = TryCast(Container, FrameworkElement)
    If fm IsNot Nothing Then
        resTemplate = fm.TryFindResource(dKey)
        If resTemplate Is Nothing AndAlso fm.GetVisualParent(Of FrameworkElement) IsNot Nothing Then
            Return DataTemplateForType(fm.GetVisualParent(Of FrameworkElement), dataType)
        Else
            Return resTemplate
        End If
    End If
    Return Nothing

End Function