定义与 .NET XAML 服务一起使用的自定义类型

定义作为业务对象或不依赖于特定框架的类型的自定义类型时,可以遵循某些 XAML 最佳做法。 如果遵循这些做法,.NET XAML 服务及其 XAML 读取器和 XAML 写入器可以发现类型的 XAML 特征,并使用 XAML 类型系统在 XAML 节点流中为其提供适当的表示形式。 本主题介绍类型定义、成员定义以及 CLR 对类型或成员的属性设置的最佳做法。

XAML 的构造函数模式和类型定义

若要在 XAML 中实例化为对象元素,自定义类必须满足以下要求:

  • 自定义类必须是公共类,并且必须公开一个无参数的公共构造函数。 (有关结构注释,请参阅下节内容。)

  • 自定义类不得为嵌套类。 完整名称路径中额外的“点”会使类-命名空间划分不明确,并干扰其他 XAML 功能,例如附加属性。 如果对象可以实例化为对象元素,则创建的对象可以填充任何将该对象作为其基础类型的属性的属性元素形式。

如果启用值转换器,仍可以为不符合这些条件的类型提供对象值。 有关详细信息,请参阅 XAML 的类型转换器和标记扩展

结构

始终可以通过 CLR 定义在 XAML 中构造结构。 这是因为 CLR 编译器可为结构隐式创建无参数构造函数。 此构造函数将所有属性值初始化为其默认值。

在某些情况下,结构并不需要默认构造行为。 这可能是因为该结构旨在填充值并在概念上充当联合。 作为联合,包含的值可能具有相互排斥的解释,因此,其属性均不可设置。 在 WPF 词汇中,GridLength 就是这样一种结构。 此类结构应实现类型转换器,以便通过属性形式表达值,方法是使用创建结构值的不同解释或模式的字符串约定。 结构还应通过非无参数构造函数对代码构造公开类似的行为。

接口

接口可以用作成员的基础类型。 XAML 类型系统检查可分配列表,并期望作为值提供的对象可以分配给接口。 只要相关的可分配类型支持 XAML 构造要求,就没有接口必须如何呈现为 XAML 类型的概念。

工厂方法

工厂方法是 XAML 2009 的一项功能。 它们修改了对象必须具有无参数构造函数的 XAML 原则。 本文未介绍工厂方法。 请参阅 x:FactoryMethod 指令

枚举

枚举具有 XAML 本机类型转换行为。 XAML 中指定的枚举常量名称根据基础枚举类型进行解析,并将枚举值返回到 XAML 对象写入器。

XAML 支持对应用了 FlagsAttribute 的枚举使用标记样式。 有关详细信息,请参阅 XAML 语法详述。 (XAML 语法详述是为 WPF 受众编写的,但该主题中的大部分信息都与 XAML 相关,并不特定于特定的实现框架。)

成员定义

类型可以定义 XAML 用法的成员。 类型可以定义可使用 XAML 的成员,即使该特定类型不能使用 XAML 也是如此。 这都是因为 CLR 继承。 只要继承成员的某些类型支持将 XAML 用作类型,并且成员支持对其基础类型使用 XAML 或具有可用的本机 XAML 语法,则该成员可使用 XAML。

属性

如果使用典型的 CLR getset 访问器模式以及与语言相对应的关键字将属性定义为公共 CLR 属性,则 XAML 类型系统可以将该属性报告为具有为 XamlMember 属性提供的适当信息的成员,例如 IsReadPublicIsWritePublic

特定属性可以通过应用 TypeConverterAttribute 启用文本语法。 有关详细信息,请参阅 XAML 的类型转换器和标记扩展

在没有文本语法或本机 XAML 转换以及没有进一步间接功能(例如标记扩展用法)的情况下,属性的类型(在 XAML 类型系统中为 TargetType)必须能够通过将目标类型视为 CLR 类型,将实例返回到 XAML 对象写入器。

如果使用 XAML 2009,并且不满足上述注意事项,则可以使用 x:Reference 标记扩展来提供值;但是,这更像是用法问题,而不是类型定义问题。

事件

如果将事件定义为公共 CLR 事件,则 XAML 类型系统可以将事件报告为 IsEventtrue 的成员。 连接事件处理程序不在 .NET XAML 服务的能力范围内;连接操作由特定的框架和实现完成。

方法

方法的内联代码不是默认的 XAML 功能。 在大多数情况下,不直接引用 XAML 中的方法成员,XAML 中方法的作用只是为特定的 XAML 模式提供支持。 x:FactoryMethod 指令是一个例外。

字段

CLR 设计准则不建议使用非静态字段。 对于静态字段,只能通过 x:Static 标记扩展访问静态字段值;在这种情况下,无需在 CLR 定义中执行任何特殊操作来公开 x:Static 用法的字段。

可附加成员

可附加成员通过定义类型的访问器方法模式向 XAML 公开。 定义类型本身不需要可作为对象使用 XAML。 事实上,常见的模式是声明一个服务类,其作用是拥有可附加成员并实现相关行为,但不提供其他功能,例如 UI 表示形式。 在以下部分中,占位符 PropertyName 表示可附加成员的名称。 该名称在 XamlName 语法中必须有效。

请注意这些模式与类型的其他方法之间的名称冲突。 如果存在与其中一种模式匹配的成员,则 XAML 处理器可以将其解释为可附加成员使用途径(即使这不是你的意图)。

GetPropertyName 访问器

GetPropertyName 访问器的签名必须为:

public static object GetPropertyName(object target)

  • target 对象在实现中可以指定为更具体的类型。 你可以使用此对象来确定可附加成员的使用范围;超出预期范围的用法将引发无效的强制转换异常,然后由 XAML 分析错误显示。 参数名称 target 不是必需的,但在大多数实现中按约定命名为 target

  • 返回值在实现中可以指定为更具体的类型。

若要针对可附加成员的属性用法支持启用了 TypeConverter 的文本语法,请将 TypeConverterAttribute 应用于 GetPropertyName 访问器。 应用于 get 而不是 set 可能看起来不直观;但是,此约定可以支持可序列化的只读可附加成员的概念,这在设计器方案中很有用。

SetPropertyName 访问器

SetPropertyName 访问器的签名必须为:

public static void SetPropertyName(object target, object value)

  • target 对象在实现中可以指定为更具体的类型,其逻辑和结果与上一部分中所述相同。

  • value 对象在实现中可以指定为更具体的类型。

请记住,此方法的值是来自 XAML 用法的输入,通常采用属性形式。 采用属性形式时,必须对文本语法提供值转换器支持,并且对 GetPropertyName 访问器进行属性设置。

可附加成员存储

仅凭访问器方法,通常无法将可附加成员值放入对象图中,也无法从对象图中检索值并将其正确序列化。 若要提供此功能,先前访问器签名中的 target 对象必须能够存储值。 存储机制应符合可附加成员原则,即,成员可附加到可附加成员不在成员列表中的目标。 .NET XAML 服务通过 API IAttachedPropertyStoreAttachablePropertyServices 为可附加成员存储提供了一种实现方法。 IAttachedPropertyStore 由 XAML 写入器用来发现存储实现,并且应该在作为访问器 target 的类型上实现。 静态 AttachablePropertyServices API 在访问器主体中使用,并通过 AttachableMemberIdentifier 引用可附加成员。

若要将 XAML 类型系统信息报告给 .NET XAML 服务,为类型、成员和程序集正确设置属性非常重要。 如果符合以下任一情况,则应当报告 XAML 类型系统信息:

  • 你打算将类型用于直接基于 .NET XAML 服务 XAML 读取器和 XAML 写入器的 XAML 系统。
  • 定义或使用基于这些 XAML 读取器和 XAML 写入器的 XAML 使用框架。

如需获取与自定义类型的 XAML 支持相关的每个 XAML 相关属性的列表,请参阅自定义类型和库的 XAML 相关 CLR 属性

使用情况

自定义类型的用法要求标记作者必须为包含自定义类型的程序集和 CLR 命名空间映射前缀。 本主题未介绍此过程。

访问级别

XAML 提供一种方法来加载和实例化具有 internal 访问级别的类型。 提供此功能是为了让用户代码可以定义自己的类型,然后通过也属于同一用户代码范围的标记实例化这些类。

来自 WPF 的示例如下:每当用户代码定义 UserControl 时,其目的是作为重构 UI 行为的一种方式,而不是作为任何可能的扩展机制的一部分,这类机制可能通过使用 public 访问级别声明支持类来暗示。 如果支持代码编译到引用为 XAML 类型的同一程序集,则可以使用 internal 访问级别声明此类 UserControl

对于在完全信任下加载 XAML 并使用 XamlObjectWriter 的应用程序,始终支持加载具有 internal 访问级别的类。

对于在部分信任下加载 XAML 的应用程序,可以使用 XamlAccessLevel API 控制访问级别特征。 此外,延迟机制(例如 WPF 模板系统)必须能够传播任何访问级别权限,并保留它们以供最终运行时评估;这是通过传递 XamlAccessLevel 信息在内部处理的。

WPF 实现

WPF XAML 使用部分信任访问模型,在该模型中,如果在部分信任下加载 BAML,则对作为 BAML 源的程序集的访问限制为 AssemblyAccessTo。 对于延迟,WPF 使用 IXamlObjectWriterFactory.GetParentSettings 作为传递访问级别信息的机制。

在 WPF XAML 术语中,内部类型是由同时包含引用 XAML 的同一程序集定义的类型。 这种类型可以通过 XAML 命名空间进行映射,该命名空间故意省略了映射的 assembly= 部分,例如 xmlns:local="clr-namespace:WPFApplication1"。 如果 BAML 引用内部类型并且该类型具有 internal 访问级别,则会为程序集生成 GeneratedInternalTypeHelper 类。 若要避免 GeneratedInternalTypeHelper,则必须使用 public 访问级别,或者必须将相关类分解为单独的程序集并将其设置为依赖程序集。

另请参阅