如何以编程方式检索用于UI层次结构中特定元素的DataTemplate?

5
我们需要在代码中确定给定特定数据类型和元素时,自动应用哪个模板到绑定的元素。我们不是寻找DataTemplateSelector,因为它是用于基于自定义逻辑告诉UI要使用哪个模板来渲染对象。相反,我们正在询问UI,对于给定的数据类型和UI元素,它将使用哪个模板。换句话说,我们正在寻找WPF基于窗口资源部分中定义的模板应用了什么,这些模板可以被该窗口上控件的资源所覆盖,也可以通过在该元素上直接设置DataTemplate或提供DataTemplateSelector来覆盖。此外,我们尝试了SelectTemplate的默认实现,但它返回null,所以我们无法采用这种方法。一个测试是询问一个没有在UI任何地方定义数据模板或选择器的元素:“你将如何显示这个值?”希望它会返回一个包含TextBlock的定义的DataTemplate,其中文本属性设置为该对象的ToString方法,这是在没有定义其他内容时默认显示的。

1
数据模板选择器怎么样?[http://msdn.microsoft.com/zh-cn/library/system.windows.controls.datatemplateselector.aspx] - pufferfish
选择器不是用来确定返回什么的吗?我想使用WPF本来会解析出的内容。 - Mark A. Donohoe
3个回答

1

Thomas Levesque的未经测试的解决方案对我来说不太适用,但提供了一个很好的起点。在我们的情况下,“container”参数并不总是在可视树中,因此首先我们要沿着逻辑树向上走,直到找到一个可视元素。这与MarqueIV的优秀建议相结合,导致了一个相当简单的解决方案。

以下代码在我生产环境中有效。您的效果可能会有所不同。 :)

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

我实际上想到了一种更简单的方法来完成这个任务,但它使用了与你的相同的功能。在我的情况下,我们只需创建一个新的DataTemplateKey,然后在元素上调用FindResource,指定该键。机制似乎与你在这里进行的操作相同,只是我们依赖于已经存在的资源查找。如果您更新/简化您的答案以按照此方式工作,我将给予您信用,因为您正在正确地使用DataTemplateKey。 - Mark A. Donohoe
哇,这个主意真是太好了,简单明了,不需要重新发明轮子,最重要的是对我来说,用户代码更少!甚至可能表现得更好,虽然我现在没有进行基准测试的条件。谢谢! - karfus
很高兴能帮忙!我必须感谢 Stack 在一段时间前教我有关 DataTemplateKey 对象的知识。一旦我了解了它们(我之前错误地尝试使用类型作为键,就像在样式等中所做的那样),其他所有事情都水到渠成了。 - Mark A. Donohoe
这个想法是可行的,但似乎与WPF自己的查找方式有一个关键的区别:如果A派生自B并且您为类型A编写了DataTemplate(因此DataType={x:Type A}),WPF将自动为B的实例使用该DataTemplate。但是,TryFindResource(new DataTemplateKey(typeof(B)))将无法找到DataTemplate。 - stijn

0

我猜你可以尝试手动重现 WPF 用于查找适当的 DataTemplate 的逻辑,通过沿着可视树向上遍历并查找带有适当键的资源。这是一个可能的实现(未经测试):

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

我使用了和karfus相同的方法,并在baseType中添加了search datattemplate,以防Type.Basetype有相关的datatemplate。

<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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接