TypeConverter 和 XAML

本主题介绍将从字符串进行的类型转换作为常规 XAML 语言功能的用途。 在 .NET Framework 中,TypeConverter 类是在 XAML 特性用法中可用作属性值的托管自定义类的实现的一部分,具有特定的用途。 如果编写自定义类,并且希望该类的实例可用作 XAML 可设置属性值,则可能需要向该类应用 TypeConverterAttribute 和/或编写自定义 TypeConverter 类。

类型转换概念

XAML 和字符串值

在 XAML 文件中设置特性值时,该值的初始类型是纯文本形式的字符串。 甚至其他基元(如 Double )最初对于 XAML 处理器都是文本字符串。

XAML 处理器需要两条信息来处理特性值。 第一条信息是所设置的属性的值类型。 定义特性值以及在 XAML 中进行处理的任何字符串都必须最终转换或解析为该类型的值。 如果值是 XAML 分析器可理解的基元(如数值),则会尝试直接转换字符串。 如果值是枚举,则字符串用于检查是否存在与该枚举中的命名常量匹配的名称。 如果值既不是分析器可理解的基元,也不是枚举,则所讨论的类型必须能够基于转换后的字符串提供类型的实例或值。 可通过指示类型转换器类达到此目的。 类型转换器实际上是提供其他类的值的帮助器类,可用于的 XAML 方案和 .NET 代码中的代码调用。

在 XAML 中使用现有的类型转换行为

你可能已经在基础应用程序 XAML 中使用了类型转换行为,只是你还不知道,具体取决于你对基础 XAML 概念的熟悉程度。 例如,WPF 定义了成百上千个采用 Point 类型的值的属性。 Point 是描述二维坐标空间中坐标的值,它实际上只有两个重要属性:XY。在 XAML 中指定一个点时,可将其指定为一个字符串,其中在所提供的 XY 值之间有分隔符(通常为英文逗号)。 例如:<LinearGradientBrush StartPoint="0,0" EndPoint="1,1"/>

即使是 Point 这种简单的类型及其在 XAML 中的简单用法也会涉及到类型转换器。 在本例中为类 PointConverter

在类级别定义的 Point 的类型转换器简化了所有采用 Point 属性的标记用法。 如果没有类型转换器,那么对于前面显示的同一示例,将需要更冗长的标记,如下所示:

<LinearGradientBrush>
  <LinearGradientBrush.StartPoint>
    <Point X="0" Y="0"/>
  </LinearGradientBrush.StartPoint>
  <LinearGradientBrush.EndPoint>
    <Point X="1" Y="1"/>
  </LinearGradientBrush.EndPoint>
</LinearGradientBrush>

使用类型转换字符串或使用更复杂的等效语法通常是编码风格的选择。 XAML 工具工作流也可能会影响值的设置方式。 某些 XAML 工具可能会生成最复杂的标记窗体,以便更容易往返于设计器视图或其自身的序列化机制。

通过检查确认类(或属性)中是否存在已应用的 TypeConverterAttribute,一般可以在 WPF 和 .NET Framework 类型中发现现有的类型转换器。 此属性将对支持类型转换器转换类型的值的类进行命名,用于 XAML 或其他可能的用途。

类型转换器和标记扩展

标记扩展和类型转换器根据 XAML 处理器行为及其应用场景来实现正交角色。 尽管上下文可用于标记扩展用途,但通常不会在标记扩展实现中检查属性的类型转换行为(其中标记扩展提供了一个值)。 换言之,即使标记扩展返回一个文本字符串作为其 ProvideValue 输出,该字符串上应用于特定属性或属性值类型的类型转换行为也不会被调用。通常,标记扩展的目的是在不调用任何类型转换器的情况下,处理字符串和返回对象。

需要标记扩展而不是类型转换器的一种常见情况是使对已存在的对象进行引用。 无状态类型转换器最多只能生成新实例,这可能并不理想。 若要深入了解标记扩展,请参阅标记扩展和 WPF XAML

本机类型转换器

在 XAML 分析器的 WPF 和 .NET XAML 实现中,某些特定类型具有本机类型转换处理,但在传统上可能不会将这些类型视为基元。 这种类型的一个示例是 DateTime。 造成这种情况的原因之一是 .NET Framework 体系结构的工作原理:类型 DateTime 在 mscorlib(.NET 中最基本的库中)中定义。 不允许 DateTime 使用来自引入依赖关系的另一个程序集的特性进行特性化(TypeConverterAttribute 来自系统);因此,无法支持通过特性化进行的正常类型转换器发现机制。 相反,XAML 分析器具有需要此类本机处理的类型的列表,它可通过与真正基元的处理方式类似的方式来处理这些类型。 (对于 DateTime,这种处理涉及调用 Parse。)

实现类型转换器

TypeConverter

在前面给出的 Point 示例中,提到了 PointConverter 类。 对于 XAML 的 .NET 实现,用于 XAML 目的的所有类型转换器都是从 TypeConverter 基类派生的类。 TypeConverter 类存在于 XAML 出现之前的 .NET Framework 版本中;其原始用法之一是为可视化设计器中的属性对话框提供字符串转换。 对于 XAML,TypeConverter 扮演的角色已扩大,包括作为用于分析字符串特性值的 to-string 和 from-string 转换的基类,还可能包括处理特定对象属性的运行时值,使其恢复为特性形式的序列化字符串。

TypeConverter 定义了四个成员,它们对于针对 XAML 处理用途的转换为字符串和从字符串转换是相关的:

其中,最重要的方法是 ConvertFrom。 此方法将输入字符串转换为所需的对象类型。 严格来说,可以实施 ConvertFrom 方法,以将更广泛的类型转换为转换器的预期目标类型,并因此实现超越 XAML 的目的(如支持运行时转换),但出于 XAML 目的,该方法仅仅作为可以处理重要的 String 输入的代码路径。

第二重要的方法是 ConvertTo。 如果将应用程序转换为标记表示形式(例如,如果它保存为文件形式的 XAML),则 ConvertTo 负责生成标记表示形式。 在这种情况下,对于 XAML 而言,重要的代码路径是传递 StringdestinationType 时使用的路径。

CanConvertToCanConvertFrom 是在服务查询 TypeConverter 实现的功能时使用的支持方法。 必须实现这些方法以便为转换器的等效转换方法支持的特定于类型的情况返回 true。 对于 XAML 用途,这通常意味着 String 类型。

XAML 的区域性信息和类型转换器

每个 TypeConverter 实现对于什么内容构成对转换有效的字符串都可以有其自己的解释,还可以使用或忽略作为参数传递的类型描述。 对于区域性和 XAML 类型转换,有一个重要的注意事项。 XAML 完全支持使用可本地化的字符串作为特性值。 但不支持将该可本地化字符串用作具有特定区域性要求的类型转换器输入,因为 XAML 特性值的类型转换器包含一个必要的固定语言分析行为,该行为使用 en-US 区域性。 有关此限制的设计原因的更多信息,请查阅 XAML 语言规范 ([MS-XAML]

区域性可能会产生问题的示例之一是,某些区域性使用逗号作为数字的小数点分隔符。 这将与许多 WPF XAML 类型转换器所具有的使用逗号作为分隔符的行为发生冲突(根据常用的 X,Y 形式等过去的先例,或逗号分隔的列表)。 即使在周围的 XAML 中传递区域性(将 Languagexml:lang 设置为 sl-SI 区域性,以此方式使用逗号作为小数点的区域性的一个示例)也不能解决此问题。

实现 ConvertFrom

若要能够用作支持 XAML 的 TypeConverter 实现,该转换器的 ConvertFrom 方法必须接受字符串作为 value 参数。 如果字符串采用有效格式,并且可以由 TypeConverter 实现进行转换,则返回对象必须支持强制转换为属性预期的类型。 否则, ConvertFrom 实现必须返回 null

每个 TypeConverter 实现对于什么内容构成对转换有效的字符串都可以有其自己的解释,还可以使用或忽略作为参数传递的类型描述或区域性上下文。 但是,WPF XAML 处理可能不会在所有情况下都将值传递给类型说明上下文,还可能不会基于 xml:lang 传递区域性。

注意

请勿使用大括号字符(尤其是 {)作为字符串格式的可能元素。 这些字符保留作为标记扩展序列的入口和出口。

实现 ConvertTo

ConvertTo 可能用于序列化支持。 通过 ConvertTo 为自定义类型及其类型转换器实现的序列化支持不是绝对要求。 但是,如果要实现控件,或使用序列化作为类的功能或设计的一部分,则应实现 ConvertTo

若要能够用作支持 XAML 的 TypeConverter 实现,该转换器的 ConvertTo 方法必须接受支持的类型(或值)的实例作为 value 参数。 如果 destinationType 参数为 String 类型,则返回对象必须可强制转换为 String。 返回字符串必须表示 value 的序列化值。 理想情况下,如果将该字符串传递到同一转换器的 ConvertFrom 实现,选择的序列化格式应能够生成相同的值,同时不会丢失大量信息。

如果值无法进行序列化,或者转换器不支持序列化,则 ConvertTo 实现必须返回 null,并允许在此情况下引发异常。 但如果引发了异常,则应报告无法使用该转换作为 CanConvertTo 实现的一部分,以便支持首先检查 CanConvertTo 以避免异常的最佳实践。

如果 destinationType 参数不属于 String 类型,则你可以选择自己的转换器处理。 通常,你要恢复使用基实现处理方法,该方法在最基本的 ConvertTo 中会引发特定的异常。

实现 CanConvertTo

对于 CanConvertTo 类型的 truedestinationType 实现应返回 String,否则遵从基实现。

实现 CanConvertFrom

对于 CanConvertFrom 类型的 truesourceType 实现应返回 String,否则遵从基实现。

应用 TypeConverterAttribute

为了使自定义类型转换器由 XAML 处理器用作自定义类的操作类型转换器,必须将 TypeConverterAttribute 应用于类定义。 通过特性指定的 ConverterTypeName 必须是自定义类型转换器的类型名。 应用此特性后,当 XAML 处理器处理属性类型使用自定义类类型的值时,它可以输入字符串并返回对象实例。

还可以基于每个属性提供类型转换器。 应将 TypeConverterAttribute 应用于属性定义(主定义,不是它包含的 get/set 的实现),而不是将它应用于类定义。 属性的类型必须与自定义类型转换器处理的类型匹配。 应用此特性时,当 XAML 处理器处理该属性的值时,它可以处理输入字符串并返回对象实例。 如果选择使用来自 Microsoft .NET Framework 或某种其他库(在其中无法控制类定义并且无法应用 TypeConverterAttribute)的属性类型,则每属性类型转换器方法特别有用。

另请参阅