WPF 的 XAML 和自定义类

在公共语言运行时(CLR)框架中实现的 XAML 支持使用任何公共语言运行时 (CLR) 语言定义自定义类或结构,然后使用 XAML 标记访问该类。 可以将 Windows Presentation Foundation(WPF)定义的类型和同一标记文件中的自定义类型混合使用,通常通过将自定义类型映射到 XAML 命名空间前缀。 本主题讨论自定义类必须满足的要求才能用作 XAML 元素。

应用程序或程序集中的自定义类

可以通过两种不同的方式定义 XAML 中使用的自定义类:在代码隐藏或其他生成主 Windows Presentation Foundation (WPF) 应用程序的代码中,或作为单独的程序集中的类(例如用作类库的可执行文件或 DLL)中的类。 其中每个方法都有特定的优点和缺点。

  • 创建类库的优点是,任何此类自定义类都可以跨许多不同的应用程序共享。 单独的库还使应用程序的版本控制问题更易于控制,并简化了创建一个类,其中预期类用法是 XAML 页面上的根元素。

  • 在应用程序中定义自定义类的优点是,此方法相对轻量,在引入单独的程序集(超出主应用程序可执行文件)时,将遇到的部署和测试问题降到最低。

  • 无论是在同一程序集中定义还是不同的程序集中定义,都需要在 CLR 命名空间和 XML 命名空间之间映射自定义类,才能在 XAML 中用作元素。 请参阅 WPF XAML 的 XAML 命名空间和命名空间映射

自定义类作为 XAML 元素的要求

为了能够实例化为对象元素,类必须满足以下要求:

  • 自定义类必须是公共类,并且支持默认(无参数)公共构造函数。 (有关结构的说明,请参阅以下部分。

  • 自定义类不能是嵌套类。 嵌套类及其常规 CLR 用法语法中的“dot”会干扰其他 WPF 和/或 XAML 功能,例如附加属性。

除了启用对象元素语法外,对象定义还为将该对象作为值类型的任何其他公共属性启用属性元素语法。 这是因为对象现在可以实例化为对象元素,并且可以填充此类属性的属性元素值。

结构

定义为自定义类型的结构始终能够在 WPF 中的 XAML 中构造。这是因为 CLR 编译器会为将所有属性值初始化为其默认值的结构隐式创建无参数构造函数。 在某些情况下,结构的默认构造行为和/或对象元素的用法并不理想。 这可能是因为结构旨在以概念方式填充值和函数作为并集,其中包含的值可能具有相互排斥的解释,因此其属性都不可设置。 此类结构的 WPF 示例为 GridLength。 通常,此类结构应实现类型转换器,以便可以使用创建结构值的不同解释或模式的字符串约定以属性形式表示值。 该结构还应通过非无参数构造函数公开代码构造的类似行为。

自定义类作为 XAML 属性的属性的要求

属性必须引用按值类型(如基元),或者对具有无参数构造函数的类型或 XAML 处理器可以访问的专用类型转换器使用类。 在 CLR XAML 实现中,XAML 处理器通过对语言基元的本机支持,或通过在后端类型定义中对类型或成员应用 TypeConverterAttribute 来查找此类转换器。

或者,该属性可以引用抽象类类型或接口。 对于抽象类或接口,XAML 分析的预期是,属性值必须填充实现接口的具体类实例,或派生自抽象类的类型实例。

属性可以在抽象类上声明,但只能在派生自抽象类的具体类上设置。 这是因为为类创建对象元素需要类上有一个公共的无参数构造函数。

已启用 TypeConverter 的属性语法

如果在类级别提供专用的属性类型转换器,则所应用的类型转换将为需要实例化该类型的任何属性启用属性语法。 类型转换器不启用该类型的对象元素用法;仅存在该类型的无参数构造函数可启用对象元素用法。 因此,启用类型转换器的属性通常不能在属性语法中使用,除非类型本身也支持对象元素语法。 例外情况是可以指定属性元素语法,但属性元素包含字符串。 该用法实质上等同于属性语法用法,这种用法并不常见,除非需要更可靠的空格处理属性值。 例如,下面是采用字符串的属性元素用法,以及属性用法等效项:

<Button>Hallo!
  <Button.Language>
    de-DE
  </Button.Language>
</Button>
<Button Language="de-DE">Hallo!</Button>

允许属性语法但不允许通过 XAML 使用包含对象元素的属性元素语法的示例是采用 Cursor 该类型的各种属性。 该 Cursor 类具有专用类型转换器 CursorConverter,但不公开无参数构造函数,因此 Cursor 即使实际 Cursor 类型是引用类型,也可以通过属性语法设置属性。

Per-Property 类型转换器

或者,属性本身可以在属性级别声明类型转换器。 这样,便可以通过将属性的传入字符串值作为基于适当类型的作的输入 ConvertFrom 来实例化属性类型的对象的“微型语言”。 通常,这样做是为了提供一个便捷的访问器,而不是在 XAML 中设置属性的唯一方法。 但是,还可以对想要使用现有的 CLR 类型(不提供无参数构造函数或特性化类型转换器)的属性使用类型转换器。 WPF API 中的示例是某些属性需要 CultureInfo 类型。 在这种情况下,WPF 使用现有的 Microsoft .NET Framework CultureInfo 类型来更好地解决早期版本的框架中使用的兼容性和迁移方案,但 CultureInfo 该类型不支持必要的构造函数或类型级类型转换,以便直接用作 XAML 属性值。

每当公开具有 XAML 用法的属性(特别是如果你是控件作者)时,应强烈建议使用依赖属性支持该属性。 这在使用现有的 Windows Presentation Foundation(WPF)XAML 处理器时尤其如此,因为您可以通过使用 DependencyProperty 支持来提高性能。 依赖属性将公开你的属性的属性系统功能,用户将期望使用 XAML 可访问的属性。 这包括动画、数据绑定和样式支持等功能。 有关详细信息,请参阅 自定义依赖项属性XAML 加载和依赖项属性

编写和注释类型转换器

有时需要编写自定义 TypeConverter 派生类来为属性类型提供类型转换。 有关如何派生自和创建可支持 XAML 用法的类型转换器以及如何应用 TypeConverterAttribute的说明,请参阅 TypeConverters 和 XAML

自定义类事件的 XAML 事件处理程序属性语法的要求

若要用作 CLR 事件,必须将该事件公开为支持无参数构造函数的类上的公共事件,或在可在派生类上访问事件的抽象类上公开该事件。 为了方便地用作路由事件,CLR 事件应实现显式 addremove 方法,这些方法添加和删除 CLR 事件签名的处理程序,并将这些处理程序转发到 AddHandlerRemoveHandler 方法。 这些方法将处理程序添加或删除到附加到事件的实例上的路由事件处理程序存储中。

注释

可以使用AddHandler直接注册路由事件的处理程序,并且有意地不定义公开该路由事件的CLR事件。 通常不建议这样做,因为该事件不会为附加处理程序启用 XAML 属性语法,并且生成的类将提供该类型功能不太透明的 XAML 视图。

写入集合属性

采用集合类型的属性具有 XAML 语法,可用于指定添加到集合的对象。 此语法具有两个显著功能。

  • 集合对象的对象不需要在对象元素语法中指定。 每当在 XAML 中指定采用集合类型的属性时,该集合类型的存在都是隐式的。

  • 在标记中,集合属性的子元素会被处理,成为集合的成员。 通常,通过列表/字典方法(例如 Add,或通过索引器)执行对集合成员的代码访问。 但 XAML 语法不支持方法或索引器(异常:XAML 2009 可以支持方法,但使用 XAML 2009 会限制可能的 WPF 用法;请参阅 XAML 2009 语言功能)。 集合显然是生成元素树的一项非常常见的要求,你需要通过某种方式在声明性 XAML 中填充这些集合。 因此,集合属性的子元素通过添加到作为集合属性类型值的集合中来进行处理。

.NET Framework XAML 服务的实现,因此 WPF XAML 处理器采用以下定义来界定集合属性。 属性的属性类型必须实现下列属性之一:

CLR 中的每个类型都有一种方法 Add ,XAML 处理器在创建对象图时使用该方法将项添加到基础集合。

注释

WPF XAML 处理器不支持泛型 ListDictionary 接口(IList<T> 以及 IDictionary<TKey,TValue>) 进行收集检测。 但是,可以将 List<T> 类用作基类,因为它直接实现 IListDictionary<TKey,TValue> 作为基类,因为它直接实现 IDictionary

声明采用集合的属性时,请谨慎对待如何在类型的新实例中初始化该属性值。 如果不将属性实现为依赖属性,则让属性使用调用集合类型构造函数的后备字段就足够了。 如果属性是依赖属性,则可能需要将集合属性初始化为默认类型构造函数的一部分。 这是因为依赖属性从元数据中获取其默认值,并且通常不希望集合属性的初始值是静态共享集合。 每个包含类型实例都应有一个集合实例。 有关详细信息,请参阅 自定义依赖项属性

可以为集合属性实现自定义集合类型。 由于隐式集合属性处理,自定义集合类型不需要提供无参数构造函数才能在 XAML 中隐式使用。 但是,可以选择为集合类型提供无参数构造函数。 这可以是一个值得的做法。 除非提供无参数构造函数,否则不能将集合显式声明为对象元素。 某些标记作者可能希望将显式集合视为标记样式的问题。 此外,无参数构造函数可以在创建将集合类型用作属性值的新对象时简化初始化要求。

声明 XAML 内容属性

XAML 语言定义 XAML 内容属性的概念。 在对象语法中可用的每个类都可以有一个 XAML 内容属性。 若要将属性声明为类的 XAML 内容属性,请应用 ContentPropertyAttribute 作为类定义的一部分。 将预期 XAML 内容属性的名称指定在属性 Name 中。 该属性按名称指定为字符串,而不是反射构造,例如 PropertyInfo

可以将集合属性指定为 XAML 内容属性。 这会导致使用该属性,其中对象元素可以具有一个或多个子元素,而无需任何干预集合对象元素或属性元素标记。 然后,这些元素被视为 XAML 内容属性的值,并添加到后盾集合实例。

一些现有的 XAML 内容属性使用属性类型 Object。 通过这种方式,XAML 内容属性可以接受基元值(例如 String),同时也可以接受一个引用对象的值。 如果遵循此模型,你的类型负责确定类型以及处理可能的类型。 内容类型的典型原因是 Object 支持一种简单方法,通过字符串添加对象内容(接收默认的呈现样式处理),或一种高级方法,添加对象内容并指定不同的呈现样式或附加数据。

序列化 XAML

对于某些方案(例如,如果你是控件作者),你可能还希望确保可以在 XAML 中实例化的任何对象表示形式也可以序列化回等效的 XAML 标记。 本主题未介绍序列化要求。 请参阅 控件创作概述元素树和序列化

另请参阅