XAML 和自定义类

更新:2007 年 11 月

可扩展应用程序标记语言 (XAML) 支持使用任何公共语言运行时 (CLR) 语言定义自定义类或结构,然后使用 XAML 标记(包括在同一标记文件中混合使用 Windows Presentation Foundation (WPF) 定义的 XAML 和自定义类的 XAML 标记)访问该类的功能。本主题讨论自定义类要用作 XAML 元素时所必须满足的要求。

本主题包括下列各节。

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

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

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

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

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

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

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

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

  • 自定义类必须是公共的且支持默认(无参数)公共构造函数。(托管代码结构隐式支持这样的构造函数。)

  • 自定义类不能是嵌套类(嵌套类和其语法中的“点”会干扰其他 WPF 功能,例如附加属性)。

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

结构

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

自定义类的属性 (Property) 作为 XAML 属性 (Attribute) 的要求

属性必须引用按值类型(如基元),或者为在类级别具有默认构造函数或专用类型转换器的类型使用类。

或者,属性可以引用抽象类类型或接口。对于抽象类或接口,运行时的期望是,属性值必须使用实现该接口的实际类实例或从该抽象类派生的类实例填充。

属性可以在抽象类上声明,但只能在从抽象类派生的实际类上设置,因为创建类的对象元素竟然要求该类中有公共的默认构造函数。

启用了类型转换器的属性语法

如果在类级别提供专用的属性化类型转换器,则应用的类型转换将为任何需要实例化该类型的属性启用属性语法。类型转换器不启用该类型的对象元素用法;只有当存在该类型的默认构造函数时才会启用对象元素用法。所以,启用了类型转换器的属性一般而言在属性语法中不可用,除非该类型本身也支持对象元素语法。这一点的一个例外是,可以指定属性元素语法,但允许该属性元素包含一个字符串。该用法实质上相当于属性语法用法,这样的用法不常见,除非需要对属性值进行更可靠的空白处理。例如,以下用法是接受字符串的属性 (property) 元素用法和等效的属性 (attribute) 用法:

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

允许使用属性语法,但通过 XAML 禁止使用包含对象元素的属性元素语法的属性示例有各种接受 Cursor 类型的属性。Cursor 类有专用类型转换器 CursorConverter,但未公开默认构造函数,因此 Cursor 属性只能通过属性语法进行设置,即使实际 Cursor 类型为引用类型。

每个属性类型转换器

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

说明:

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

编写集合属性

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

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

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

WPF XAML 处理器对集合属性的构成内容使用以下定义。属性的属性类型必须实现以下内容之一:

这每一种类型都具有 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 概述

自定义的依赖项属性

控件创作概述

基元素概述

XAML 加载和依赖项属性