使用自定义类型扩展指定DataTemplate.DataType

9
我有这个标记扩展。
public class NullableExtension : TypeExtension
{
    public NullableExtension() {
    }

    public NullableExtension( string type )
        : base(type) {
    }

    public NullableExtension( Type type )
        : base(type) {
    }

    public override object ProvideValue( IServiceProvider serviceProvider ) {
        Type basis = (Type)base.ProvideValue( serviceProvider );
        return typeof(Nullable<>).MakeGenericType( basis );
    }
}

这个类型被设计为提供其他类型的可空版本。在“普通”的XAML中使用时,它的表现符合预期。例如:

<SomeControl DataContext="{My:Nullable System:Int32}"/>

假设 My 是包含扩展的 C# 命名空间定义的 XML 命名空间,System 同理。控件的数据上下文被设置为 Nullable<int>System.Type,这是我预期的。

然而,当我使用这个扩展来尝试设置 DataTemplateDataType 属性时,出现了问题。

<DataTemplate DataType="{My:Nullable System:Int32}">
  <TextBlock ... />
</DataTemplate>

我被编译器告知:

一个字典的键不能是'System.Windows.Controls.Primitives.TextBlock'类型。仅支持String、TypeExtension和StaticExtension。

还有

没有类型为'NullableExtension'的构造函数有1个参数。

有人知道为什么只允许这三种方法(甚至不包括TypeExtension的子类,比如我的)吗?在那一点上,XAML 的处理有什么特殊之处?还有没有其他方法可以完成这个任务(基于可空类型进行数据模板选择),而不需要使用DataTemplateSelector?
4个回答

11

我非常认真地研究了你的问题,以下是我的发现。

问:为什么只允许使用这三个(StringTypeExtensionStaticExtension)?

答:这是设计上的考虑。如果您可以编写任何自定义标记扩展作为字典中的键,那么会引入哪些副作用呢?考虑一下,如果您将Binding作为DataType的值...我相信您可以添加数十个与字典键动态性相关的问题。

问:在XAML处理的那一点有什么特别之处?

答:在那一点上,您有BAML创建。问题来自于内部类BamlRecordWriter,但消息并未描述实际问题。当您将自定义标记扩展指定为DataType时,它会获取DataTemplate的子元素,并检查它是否可分配为string、TypeExtension或StaticExtension(请参见BamlRecordWriter.WriteElementStart()函数)。确实。不是您的扩展(可分配给TypeExtension),而是第一个子元素(不可分配)。现在你有了这个奇怪的“不能是类型”的东西。虽然它看起来像是BamlRecordWriter的一个错误,但我认为他们故意留下了它。只要它不让您将自定义标记扩展用作DataType值,谁会在乎错误消息呢?

问:除了使用DataTemplateSelector之外,还有其他方法可以实现这一点(基于可能为空的类型进行数据模板选择)吗?

答:是的,有点类似。首先,您可以使用标准的TypeExtension来完成所有肮脏的工作:

<DataTemplate DataType="{x:Type TypeName=System:Nullable`1[[System.Int32]]}">
</DataTemplate>

但在大多数情况下(如果不是全部),您将看不到模板结果。为什么?现在涉及可空类型的装箱规则。装箱非空可空值类型会使值类型本身装箱,而不是包装值类型的System.Nullable。因此,默认模板选择器将查找带有T数据类型的DataTemplate,而不是Nullable<T>

我可能不理解您尝试使用可空扩展解决的确切问题,但您可能希望将可空类型包装到自己的ref类型中,编写一个包装器的DataTemplate,并使用DataTemplate.Triggers来选择内容外观。好吧,这看起来像是重新发明的数据模板选择器:)...

NB:我不是MS人员,我的发现基于Reflector和我自己的经验(并不像我希望的那样大alt text)。无论如何,希望我能帮到您: )。

干杯


谢谢。我意识到这可能很危险,但我认为可以验证所分配给DataType的值的类型是否实际上是System.Type。我不打算为DataType分配任何任意值,只是一个Type。关于BAML解析器很有趣。至于装箱问题,那是个好问题,虽然在我手头的情况下,我将明确提供一个Nullable<T>而不是一个装箱值,并且已经有其他支架来使它“对我的需求做正确的事情”。看起来我必须继续使用解决方法。感谢您的帮助。 - user197015
唉,我总是忘记这里的注释不会保留额外的换行符。对不起,上面的格式看起来很糟糕。 - user197015
1
自从这篇文章写出来以后,显然情况已经发生了变化。虽然看起来 DataType="{x:Type TypeName=System:Nullable\1[[System.Int32]]}"` 应该可以工作,但在2013年8月,在.NET 4.0下,它并不能正常工作。我发现唯一的解决方法是使用某种MarkupExtension。 - Dan

2
语法
DataType="{x:Type TypeName=System:Nullable`1[[System.Int32]]}">

似乎不适用于用户定义类型 :(

实际上,另一种方法是创建一个基本的非泛型类型。将第一个数据模板设置为该类型,并将ContentPresenter.Content绑定到保存T对象的属性。然后为任何T创建其他数据模板。


1
我发现了一个可行的解决方法。出于某种原因,@Anvaka是正确的:DataType属性不允许您使用自定义MarkupExtension。但是它将允许您使用自定义MarkupExtension的StaticResource。
拿出你的MarkupExtension,在其中添加一个公共默认构造函数。然后在资源中创建该扩展的实例,并直接设置属性。完成,它可以正常工作。以下是您需要执行的类似操作。
<My:Nullable x:Key="Foo" Type="{x:Type System:Int32}"/>
<DataTemplate DataType="{StaticResource Foo}">
    <TextBlock ... />
</DataTemplate>

1

这应该可以工作...

<DataTemplate DataType="{x:Type System:Nullable`1[System.Int32]}"> 
</DataTemplate> 

为了写出更好的答案,您应该在解决方案中提供一些上下文(为什么它有效?使用了哪些来源?) - Dhara

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