我已经创建了一个DataTemplateSelector,并用一组已知接口进行初始化。如果传递到选择器中的项实现了这些接口中的一个,则会返回相应的数据模板。
首先,这是相关的ICategory接口...
这是如何在资源中设置的,以及与 DataType = ICategory 相应的 HierarchicalDataTemplate...
最后,这里有一个使用它的TreeView...
我已经调试过了,并且可以确认正确的数据模板按预期返回到TreeView中,这是通过代码步进和HierarchicalDataTemplate上的ItemSource绑定加载子类别时实现的。所有这些都正常工作。
不起作用的是模板本身的内容。如您所见,该模板只需显示类别的名称,但它只是将对象原始呈现出来,就好像直接放置在ContentPresenter中一样,没有任何模板。在UI中你只能看到ToString的结果。模板的内容被完全忽略了。
我唯一能想到的是因为我正在使用DataType的接口而导致无法工作,但是,孩子们的ItemsSource绑定确实起作用,所以我有点困惑。
值得注意的是:作为测试,我创建了一个基于具体类型(即Category而不仅仅是ICategory)的第二个DataTemplate,当我这样做时,它按预期工作。问题在于具体类型位于不应被UI引用的程序集中。这就是我们首先使用接口的全部原因。
注:我还尝试过改变查找模板的方式,使用Key而不是设置DataType属性。在那种情况下,选择器仍然找到相同的资源,但仍然不起作用!
具有讽刺意味的是,如果我使用相同的键通过StaticResource绑定直接设置TreeView的ItemTemplate,那么它会起作用,这意味着仅当我从选择器返回模板时才不起作用,并且与是否设置DataType无关。
首先,这是相关的ICategory接口...
public interface ICategory
{
ICategory ParentCategory { get; set; }
string Name { get; set; }
ICategoryCollection Subcategories { get; }
}
这是一个DataTemplateSelector,它基于基类或接口进行匹配,而不仅仅是特定的具体类...
[ContentProperty("BaseTypeMappings")]
public class SubclassedTypeTemplateSelector : DataTemplateSelector
{
private delegate object TryFindResourceDelegate(object key);
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var frameworkElement = container as FrameworkElement;
foreach(var baseTypeMapping in BaseTypeMappings)
{
// Check if the item is an instance of, a subclass of,
// or implements the interface specified in BaseType
if(baseTypeMapping.BaseType.IsInstanceOfType(item))
{
// Create a key based on the BaseType, (not item.DataType as usual)
var resourceKey = new DataTemplateKey(baseTypeMapping.BaseType);
// Get TryFindResource method from either the FrameworkElement,
// or from the application
var tryFindResource = (frameworkElement != null)
? (TryFindResourceDelegate)frameworkElement.TryFindResource
: Application.Current.TryFindResource;
// Use the TryFindResource delegate from above to try finding
// the resource based on the resource key
var dataTemplate = (DataTemplate)tryFindResource(resourceKey);
dataTemplate.DataType = item.GetType();
if(dataTemplate != null)
return dataTemplate;
}
}
var defaultTemplate = DefaultDataTemplate ?? base.SelectTemplate(item, container);
return defaultTemplate;
}
public DataTemplate DefaultDataTemplate { get; set; }
public Collection<BaseTypeMapping> BaseTypeMappings { get; } = new Collection<BaseTypeMapping>();
}
public class BaseTypeMapping
{
public Type BaseType { get; set; }
}
这是如何在资源中设置的,以及与 DataType = ICategory 相应的 HierarchicalDataTemplate...
<HierarchicalDataTemplate DataType="{x:Type model:ICategory}"
ItemsSource="{Binding Subcategories}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<is:SubclassedTypeTemplateSelector x:Key="SubclassedTypeTemplateSelector">
<!--<is:BaseTypeMapping BaseType="{x:Type model:ICategory}" />-->
</is:SubclassedTypeTemplateSelector>
最后,这里有一个使用它的TreeView...
<TreeView x:Name="MainTreeView"
ItemsSource="{Binding Categories}"
ItemTemplateSelector="{StaticResource SubclassedTypeTemplateSelector}" />
我已经调试过了,并且可以确认正确的数据模板按预期返回到TreeView中,这是通过代码步进和HierarchicalDataTemplate上的ItemSource绑定加载子类别时实现的。所有这些都正常工作。
不起作用的是模板本身的内容。如您所见,该模板只需显示类别的名称,但它只是将对象原始呈现出来,就好像直接放置在ContentPresenter中一样,没有任何模板。在UI中你只能看到ToString的结果。模板的内容被完全忽略了。
我唯一能想到的是因为我正在使用DataType的接口而导致无法工作,但是,孩子们的ItemsSource绑定确实起作用,所以我有点困惑。
值得注意的是:作为测试,我创建了一个基于具体类型(即Category而不仅仅是ICategory)的第二个DataTemplate,当我这样做时,它按预期工作。问题在于具体类型位于不应被UI引用的程序集中。这就是我们首先使用接口的全部原因。
注:我还尝试过改变查找模板的方式,使用Key而不是设置DataType属性。在那种情况下,选择器仍然找到相同的资源,但仍然不起作用!
具有讽刺意味的是,如果我使用相同的键通过StaticResource绑定直接设置TreeView的ItemTemplate,那么它会起作用,这意味着仅当我从选择器返回模板时才不起作用,并且与是否设置DataType无关。