【问题标题】:Specifying DataTemplate.DataType with a custom type extension使用自定义类型扩展指定 DataTemplate.DataType
【发布时间】:2009-12-13 20:11:59
【问题描述】:

我有这个标记扩展

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 也是如此)。正如我所料,控件的数据上下文设置为System.Type for Nullable&lt;int&gt;

但是,当我使用此扩展程序尝试设置 DataTemplateDataType 属性时

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

编译器告诉我,

字典的键不能是类型 'System.Windows.Controls.Primitives.TextBlock'。只有字符串, 支持 TypeExtension 和 StaticExtension。"

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

有谁知道为什么只允许使用这三种方法(甚至像我的那样,甚至 TypeExtension 的子类都不允许)?那时处理 XAML 有什么特别之处?还有另一种方法来实现这一点(基于可能为空的类型选择数据模板)而不诉诸DataTemplateSelector

【问题讨论】:

    标签: wpf xaml datatemplate


    【解决方案1】:

    我真的很了解你的问题,这就是我的发现。

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

    答:设计。如果您可以编写任何自定义标记扩展来用作字典中的键,这会带来什么副作用?考虑您将 Bi​​nding 作为 DataType 的值...我很确定您可以添加十几个与字典键动态性质相关的问题。

    问:此时处理 XAML 有什么特别之处?

    一个。此时您已创建 BAML。问题来自内部类BamlRecordWriter,但消息并未描述实际问题。当您将自定义标记扩展指定为 DataType 时,它​​接受 DataTemplate 的 child,并检查它是否可以从字符串、TypeExtension 或 StaticExtension 分配(参见 BamlRecordWriter.WriteElementStart() 函数) .确实。不是您的扩展名(可分配给 TypeExtension),而是第一个孩子(不可分配)。现在你有了这个奇怪的“不能是类型”的东西。虽然它看起来像 BamlRecordWriter 的错误,但我认为他们是故意留下的。除非它不允许您使用自定义标记扩展作为 DataType 值,否则谁在乎错误消息?

    问:是否有另一种方法可以在不借助 DataTemplateSelector 的情况下完成此操作(基于可以为空的类型选择数据模板)?

    答:是的,有点。首先,您可以让标准的 TypeExtension 为您完成所有繁琐的工作:

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

    但在大多数情况下(如果不是一直),您不会看到模板结果。为什么?现在谈到可空类型的装箱规则。对非 null 可为空的值类型进行装箱是对值类型本身进行装箱,而不是对包装值类型的 System.Nullable 进行装箱。因此,默认模板选择器将查找 DataTemplate 的 DataType 为 T 而不是 Nullable&lt;T&gt;

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

    注意:我不是 MS 人,我的发现是基于 Reflector 和我自己的经验(没有我希望的那么大 )。无论如何,希望我能帮上忙:)。

    干杯

    【讨论】:

    • 谢谢。我意识到这可能很危险,但我认为可以验证分配给 DataType 的值的类型实际上是 System.Type。我不是想为 DataType 分配任何任意值,只是一个类型。对 BAML 解析器很感兴趣。至于装箱问题,这是一个很好的观点,尽管在我得到的情况下,我将明确提供 Nullable 而不是装箱值,并准备好其他脚手架以使其“做正确的事”我的需要。看来我将不得不继续解决方法。感谢您的帮助。
    • 呃,我总是忘记 cmets 不会在这里保留额外的换行符。抱歉,上面的格式太糟糕了。
    • 自从写这篇文章以来,事情显然发生了变化,虽然看起来DataType="{x:Type TypeName=System:Nullable`1[[System.Int32]]}" 应该可以工作,但在 2013 年 8 月,在 .NET 4.0 下,它没有。我发现解决此问题的唯一方法是使用某种 MarkupExtension。
    【解决方案2】:

    语法

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

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

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

    【讨论】:

      【解决方案3】:

      这应该可以...

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

      【讨论】:

      • 要写出更好的答案,您应该提供解决方案的一些背景信息(为什么有效?您使用了什么来源?)
      【解决方案4】:

      我为此找到了一个讨厌的解决方法。无论出于何种原因,@Anvaka 都是正确的:DataType 属性不允许您使用自定义 MarkupExtension。但它将允许您使用自定义 MarkupExtension 的 StaticResource。

      使用您的 MarkupExtension,为其添加一个公共默认构造函数。然后在资源中创建扩展的实例,直接设置属性。繁荣,它需要它。以下与您需要做的类似

      <My:Nullable x:Key="Foo" Type="{x:Type System:Int32}"/>
      <DataTemplate DataType="{StaticResource Foo}">
          <TextBlock ... />
      </DataTemplate>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-29
        • 1970-01-01
        • 2019-08-14
        • 2013-05-29
        相关资源
        最近更新 更多