定义用于 .NET XAML 服务的自定义类型

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

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

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

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

  • 自定义类不能是嵌套类。 全名路径中的额外“dot”使类命名空间划分不明确,并干扰其他 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 Markup Extension 可用于提供值(如果未满足上述注意事项);但是,这比类型定义问题更多的是使用问题。

事件

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

方法

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

领域

CLR 设计准则禁止使用非静态字段。 对于静态字段,只能通过 x:Static Markup Extension; 访问静态字段值;在这种情况下,你未在 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提供可附加成员存储的实现技术。 XAML 编写器使用 IAttachedPropertyStore 来发现存储实现,应在访问器 target 类型上实现。 静态 AttachablePropertyServices API 在访问器的正文中使用,并通过其 AttachableMemberIdentifier引用可附加成员。

正确关联类型、成员和程序集非常重要,以便将 XAML 类型系统信息报告给 .NET XAML 服务。 如果以下任一情况适用,则报告 XAML 类型系统信息是相关的:

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

有关与 XAML 支持自定义类型相关的每个 XAML 相关属性的列表,请参阅 XAML-Related 自定义类型和库的 CLR 属性

用法

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

访问级别

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

WPF 中的一个示例是,每当用户代码定义一个 UserControl,该 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 的同一程序集定义的类型。 此类类型可以通过有意省略映射的 assembly= 部分的 XAML 命名空间进行映射,例如,xmlns:local="clr-namespace:WPFApplication1"。 如果 BAML 引用内部类型并且该类型具有 internal 访问级别,则这将为程序集生成 GeneratedInternalTypeHelper 类。 如果要避免 GeneratedInternalTypeHelper,则必须使用 public 访问级别,或者必须将相关类分解为单独的程序集,并使该程序集依赖于该程序集。

另请参阅