当在设计平面上选中某个组件,你选中的就是这个组件的某个实例,显示在属性浏览器里的书形象都是来自这个实例。当在属性浏览器中对属性进行编辑时,新属性值也将被刷新到这个实例中。这很容易理解,但也往往不那么直观,因为属性浏览器只能把属性(不管是什么类型)显示为文本。在属性值在属性浏览器和那个组件实例见来回传递时,他们必须在字符串和他们真实的类型之间来回传递。
既然要进行类型转换,就需要有类型转换器(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 return base.ConvertTo(context, culture, value, destinationType);
68 }
69
70 public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) {
71
72 // Always force a new instance
73 return true;
74 }
75
76 public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) {
77
78 // Use the dictionary to create a new instance
79 return new Hand((Color)propertyValues["Color"], (int)propertyValues["Width"]);
80 }
81 }