当在设计平面上选中某个组件,你选中的就是这个组件的某个实例,显示在属性浏览器里的书形象都是来自这个实例。当在属性浏览器中对属性进行编辑时,新属性值也将被刷新到这个实例中。这很容易理解,但也往往不那么直观,因为属性浏览器只能把属性(不管是什么类型)显示为文本。在属性值在属性浏览器和那个组件实例见来回传递时,他们必须在字符串和他们真实的类型之间来回传递。
既然要进行类型转换,就需要有类型转换器(type converter),他们在.NET中主要用途就是对各种类型进行转换。.NET已经为程序员准备了内建的类型转换器,但如果你的组件或控件包含一些基于定制类型的属性――比如那个时钟控件ClockControl(源代码见《.Net窗体设计阶段的功能集成问答》 )的HourHand,MinuteHand,SecongHand属性,他们就无能为力了:

Code
1
public class Hand
{
2
private Color color = Color.Black;
3
private int width = 1;
4
public Hand(Color color, int width)
{
5
this.color = color;
6
this.width = width;
7
}
8
9
public Color Color
{
10
get
{ return color; }
11
set
{ color = value; }
12
}
13
14
public int Width
{
15
get
{ return width; }
16
set
{ width = value; }
17
}
18
}
如果没有定制的类型转换器,你只能看到如下图所示画面:
创建定制的类型转换器:
1.从TypeConverter基类派生一个新类HandConverter。
2.为了支持类型转换,HandConverter类必须对CanConvertFrom,ConvertTo和ConvertFrom方法进行覆盖。
3.为了让多值属性和嵌套属性具备展开编辑功能,可将基类改为ExpandableObjectConverter。对比效果如下图:

4.对GetCreateInstanceSupported和CreateInstance方法进行覆盖,使得当在属性浏览器里编辑嵌套属性时能立即刷新根属性。(比如你修改HourHand.Color属性,HourHand属性会立刻更新。)

完整代码
1
public class HandConverter : ExpandableObjectConverter
{
2
// Don't need to override CanConvertTo if converting to string, as that's what base TypeConverter does
3
// Do need to override CanConvertFrom since it's base converts from InstanceDescriptor
4
5
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
6
// We can be converted to an InstanceDescriptor
7
if( destinationType == typeof(InstanceDescriptor) ) return true;
8
return base.CanConvertTo(context, destinationType);
9
}
10
11
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
12
// We can convert from a string to a Hand type
13
if( sourceType == typeof(string) )
{ return true; }
14
return base.CanConvertFrom(context, sourceType);
15
}
16
17
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo info, object value)
{
18
19
// If converting from a string
20
if( value is string )
{
21
// Build a Hand type
22
try
{
23
// Get Hand properties
24
string propertyList = (string)value;
25
string[] properties = propertyList.Split(';');
26
return new Hand(Color.FromName(properties[0].Trim()),
27
Convert.ToInt32(properties[1]));
28
}
29
catch
{}
30
throw new ArgumentException("The arguments were not valid.");
31
}
32
return base.ConvertFrom(context, info, value);
33
}
34
35
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
36
37
// If source value is a Hand type
38
if( value is Hand )
{
39
// Convert to string
40
if( (destinationType == typeof(string)) )
{
41
Hand hand = (Hand)value;
42
string color = (hand.Color.IsNamedColor ?
43
hand.Color.Name :
44
hand.Color.R + ", " + hand.Color.G + ", " + hand.Color.B);
45
return string.Format("{0}; {1}", color, hand.Width.ToString());
46
}
47
// Convert to InstanceDescriptor
48
if( (destinationType == typeof(InstanceDescriptor)) )
{
49
Hand hand = (Hand)value;
50
object[] properties = new object[2];
51
Type[] types = new Type[2];
52
53
// Color
54
types[0] = typeof(Color);
55
properties[0] = hand.Color;
56
57
// Width
58
types[1] = typeof(int);
59
properties[1] = hand.Width;
60
61
// Build constructor
62
ConstructorInfo ci = typeof(Hand).GetConstructor(types);
63
return new InstanceDescriptor(ci, properties);
64
}
65
}
66
// Base ConvertTo if neither string or InstanceDescriptor required
67