编写一个 "partial" XAML TypeConverter,它可以仅转换 to/from 层次结构中的特定子类型

Write a "partial" XAML TypeConverter that can convert to/from only a specific sub-type in a hierarchy

给定一个基础 class Color 至少有两个子类型,RgbColorCmykColor:

abstract partial class Color { }

sealed class RgbColor : Color
{
    public byte R { get; set; }
    public byte G { get; set; }
    public byte B { get; set; }
}

sealed class CmykColor : Color
{
    public byte C { get; set; }
    public byte M { get; set; }
    public byte Y { get; set; }
    public byte K { get; set; }
}

和一些我要用 .NET 4 的 System.Xaml.XamlServices:

序列化 to/from XAML 的类型
class Something
{
    public Color Color { get; set; }
}

我希望能够像这样在 XAML 一侧缩写 RGB 颜色:

<Something Color="#010203" />

而不是必须输入所有内容:

<Something>
  <Something.Color>
    <RgbColor R="1" G="2" B="3" />
  </Something.Color>
</Something>

这可以通过 TypeConverter 轻松完成。 (在这个问题的末尾找到我当前的实现。)问题是我不需要,也不想要 Color 的其他子类型的特殊缩写语法,例如 CmykColor

如何为 Color 编写仅适用于其中一种子类型 RgbColorTypeConverter

(我已经尝试专门为 RgbColor 而不是 Color 编写一个 TypeConverter,但是 XAML 序列化器在它遇到 Color 属性.)


// using System;
// using System.ComponentModel;
// using System.Globalization;

[TypeConverter(typeof(ColorConverter))]
partial class Color { }

sealed class ColorConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        // omitted for brevity's sake
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        Debug.Assert(value is Color);
        if (destinationType == typeof(string))
        {
            if (value is RgbColor)
            {
                var color = (RgbColor)value;
                return string.Format("#{0:x2}{1:x2}{2:x2}", color.R, color.G, color.B);
            }
            else
            {
                throw new NotSupportedException(); // ? 
            }
        }
        else
        {
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
}

事实证明,有一种不同类型的转换器可以用于此目的:ValueSerializer。值序列化器在两个关键方面不同于类型转换器:

  • 它们只能转换 to/from 个字符串。 (无论如何,其他类型与 XAML 无关。)
  • 他们在 CanConvert... 阶段已经收到要转换的对象。 (这是重要的一点。)

我在使用值序列化器时遇到的主要问题是让它们被 XamlServices 调用。

这是我的解决方案:

首先,您不仅要应用值序列化器,还要应用类型转换器到基 class:

// both types of converters must be applied!
[TypeConverter(typeof(ColorConverter))]
[ValueSerializer(typeof(ColorValueSerializer))]
partial class Color { }

没有类型转换器属性,将永远不会调用值序列化程序!

接下来,必须编写类型转换器,使其仅从字符串 (what MSDN calls "the load path") 进行转换:

sealed class ColorConverter : TypeConverter
{
    public override bool CanConvertFrom(..., Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(...);
    }

    public override bool CanConvertTo(..., Type destinationType)
    {
        return destinationType != typeof(string) && base.CanConvertTo(...);
        // the above != is not a typo! We must not let
        // the type converter do the conversion to string.
    }

    public override object ConvertFrom(...)
    {
        // parse a #rrggbb string and return the corresponding RgbColor
    }
}

最后,让值序列化程序处理到字符串的转换 (the "save path"):

sealed class ColorValueSerializer : ValueSerializer
{
    public override bool CanConvertToString(object value, ...)
    {
        return value is RgbColor;
    }

    public override string ConvertToString(object value, ...)
    {
        // convert RgbColor into "#{r}{g}{b}" string
    }
}