编写一个 "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
至少有两个子类型,RgbColor
和 CmykColor
:
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
编写仅适用于其中一种子类型 RgbColor
的 TypeConverter
?
(我已经尝试专门为 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
}
}
给定一个基础 class Color
至少有两个子类型,RgbColor
和 CmykColor
:
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
:
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
编写仅适用于其中一种子类型 RgbColor
的 TypeConverter
?
(我已经尝试专门为 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
}
}