XAML 及 WPF 的自定义类

.NET 中实现的 Extensible Application Markup Language (XAML) 支持此功能:以任意 common language runtime (CLR) 语言定义自定义类或结构,然后使用 XAML 标记访问该类。 您可以在同一标记文件中混合使用 Windows Presentation Foundation (WPF) 定义的类型和您的自定义类型,通常是通过将自定义类型映射到 XAML 命名空间前缀。 本主题讨论自定义类要用作 XAML 元素时所必须满足的要求。

本主题包括下列各节。

  • 应用程序或程序集中的自定义类
  • 自定义类作为 XAML 元素的要求
  • 自定义类的属性作为 XAML 特性的要求
  • 有关自定义类事件的 XAML 事件处理程序特性语法的要求
  • 编写集合属性
  • 声明 XAML 内容属性
  • 序列化 XAML
  • 相关主题

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

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

  • 创建类库的优点是,任何这样的自定义类都可以在许多可能不同的应用程序中共享。 单独的库也使应用程序的版本问题更易控制,而且也简化了在 XAML 页上创建要用作根元素的类这一过程。

  • 在应用程序中定义自定义类的优点是,此方法是相对轻型的方法,可最大限度减少当引入主应用程序可执行文件之外的单独程序集时遇到的部署和测试问题。 但是,一个显著的缺点是,不能将同一程序集中定义的类用作该应用程序中其他 XAML 页的根元素。

  • 无论是在相同还是不同程序集中定义自定义类,都需要在 CLR 命名空间和 XML 命名空间之间映射这些自定义类,以便在 XAML 中将它们作为元素使用。 请参见 WPF XAML 的 XAML 命名空间和命名空间映射

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

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

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

  • 您的自定义类不得是嵌套类。 采用常规 CLR 使用语法的嵌套类和“点”会干扰其他 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 类型为引用类型。

每个属性类型转换器

此外,属性本身也可以在属性级别声明类型转换器。 对于基于适当类型的 ConvertFrom 操作,通过将特性的传入字符串值作为输入进行处理,这将启用“mini language”,它将实例化内联属性类型的对象。 通常,这样做是为了提供方便的访问器,而不是作为在 XAML 中设置属性的唯一手段。 但是,对于想要使用现有 CLR 类型(不提供默认构造函数或特性化类型转换器)的特性,也可以使用类型转换器。 例如,WPF  APIs 中接受 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 事件应实现显式 add 和 remove 方法,这两种方法分别添加和移除 CLR 事件签名的处理程序,并将这些处理程序转发到 AddHandlerRemoveHandler 方法。 这些方法在事件所附加到的实例的路由事件处理程序存储区中添加或删除处理程序。

注意注意

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

编写集合属性

接受集合类型的属性具有的 XAML 语法允许您指定要添加到集合的对象。 此语法有两种显著功能。

  • 不需要在对象元素语法中指定属于集合对象的对象。 如果在 XAML 中指定接受集合类型的属性,则隐式存在该集合类型。

  • 标记中该集合属性的子元素将被处理为集合的成员。 通常,代码对集合成员的访问通过列表/字典方法(如 Add)或索引器执行。 但 XAML 语法不支持方法或索引器。 对于生成元素树的操作,集合明显是很常见的要求,您需要在声明性 XAML 中通过某种方法填充这些集合。 因此,处理集合属性的子元素的方法是将这些子元素添加到将作为集合属性类型值的集合中。

对于集合属性的构成,.NET XAML 实现采用以下定义,因而 WPF XAML 处理程序也采用此定义。 属性的属性类型必须实现以下内容之一:

CLR 中这些类型中的每一种都具有 Add 方法,当创建对象图时,XAML 处理器使用该方法向基础集合中添加项。

注意注意

由 WPF XAML 处理器执行的集合检测功能不支持泛型 List 和 Dictionary 接口(IList<T>IDictionary<TKey, TValue>)。但是,可以将 List<T> 类用作基类(因为它直接实现 IList),或者将 Dictionary<TKey, TValue> 用作基类(因为它直接实现 IDictionary)。

声明接受集合的属性时,务必注意在该类型的新实例中初始化属性值的方式。 如果未将属性实现为依赖项属性,则使属性使用可调用集合类型构造函数的支持字段是合适的。 如果属性是依赖项属性,则可能需要将集合属性初始化为默认类型构造函数的一部分。 这是因为,依赖项属性从元数据中获取其默认值,并且您通常不希望集合属性的初始值为静态的共享集合。 对于每个包含类型实例,都应有一个集合实例。 有关更多信息,请参见自定义依赖项属性

您可以为集合属性实现自定义集合类型。 由于集合属性隐式进行处理,因此自定义集合类型不需要提供默认构造函数就可以在 XAML 中隐式使用。 但是,也可以选择为集合类型提供默认构造函数。 这是一种值得推行的做法。 除非提供默认构造函数,否则无法显式地将集合声明为对象元素。 一些标记作者可能喜欢将显式集合视作一种标记样式。 另外,在创建将集合类型用作属性值的新对象时,默认构造函数可以简化初始化要求。

声明 XAML 内容属性

XAML 语言定义了 XAML 内容属性的概念。 对象语法中可用的每个类恰好有一个 XAML 内容属性。 若要将属性声明为类的 XAML 内容属性,请将 ContentPropertyAttribute 作为类定义的一部分进行应用。 在特性中将要使用的 XAML 内容属性的名称指定为 Name

您可以将集合属性指定为 XAML 内容属性。 这将导致使用该属性,由此对象元素可以有一个或多个子元素,而没有任何插入集合对象元素或属性元素标记。 这些元素然后被作为 XAML 内容属性的值进行处理,并添加到支持集合实例中。

一些现有的 WPF XAML 内容属性使用 Object 的属性类型。 这将使 XAML 内容属性可接受基元值(如 String),并可接受单个引用对象值。 如果遵从此模型,则您的类型将负责类型确定和可能类型的处理。 使用 Object 内容类型的一般原因有两种,一种是支持将对象内容添加为字符串的简单方式(接受默认呈现处理),另一种是支持添加对象内容(指定非默认呈现或其他数据)的高级方式。

序列化 XAML

对于某些情况,例如如果您是控件作者,则可能还需要确保 XAML 中可实例化的任何对象表示形式也可以反序列化到等效 XAML。 本主题中不介绍序列化要求。 请参见控件创作概述元素树和序列化

请参见

概念

XAML 概述 (WPF)

自定义依赖项属性

控件创作概述

基元素概述

XAML 加载和依赖项属性