自定义类在PropertyGrid上的展示方法

时间:2021-01-27 00:48:49

自定义类在PropertyGrid上的展示方法

零.引言

  PropertyGrid用来显示某一对象的属性,但是并不是所有的属性都能编辑,基本数据类型(int, double等)和.Net一些封装的类型(Size,Color等)可以编辑,但是对于自己定义的类型属性,是不能编辑的,本文主要讲述如何为自定义类型作为属性时,在PropertyGrid中进行编辑,以及进行设计时序列化,本文主要参考MSDN,错误和不足之处还望指正。

一.自定义类属性

  在PropertyGrid中能够编辑的都是.Net中自己封装的类,如果在一个类中有一个属性是我们自己定义的类型,在PropertyGrid中会是怎样的呢?看下面这个例子:

  假如现在有一个类Line:

   Line

  有一个控件类包含一个Line类型的属性:

  
自定义类在PropertyGrid上的展示方法
 1 public class MyControl : System.Windows.Forms.UserControl
2 {
3 Line _line;
4
5 public MyControl()
6 {
7 _line = new Line(new Point(0, 0),new Point(100, 100));
8 }
9
10 public Line MyLine
11 {
12 get{return _line;}
13 set{_line = value;}
14 }
15
16 protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
17 {
18 e.Graphics.DrawLine(Pens.Red, this._line.Point1, this._line.Point2);
19 base.OnPaint(e);
20 }
21 }
自定义类在PropertyGrid上的展示方法

  重新生成,从工具箱中将该控件拖入Form中,查看他的属性,在PropertyGrid中显示如下:

  自定义类在PropertyGrid上的展示方法

  可见MyLine属性的值不显示,且是不能编辑的。这是因为PropertyGrid根本不知道Line是个什么类型,不知道要怎么显示,如果要其能在PropertyGrid中显示,必须给他提供转换器。

二.转换器概念

  PropertyGrid中属性的值都是以字符串的形式呈现给我们看的,显示一个对象的属性时,要将对象的属性值转换为字符串显示出来,而设置属性时,要将字符串转换为对象的属性值。这就需要一个转换器。在.Net中定义了一个TypeConverter 类,用来作为这些转换器的基类。.Net为一些类设计了专门的转换类,如:System.Drawing.ColorConverter ,System.Drawing.FontConverter等等,(具体参见MSDN中TypeConverter的继承关系)因此在PropertyGrid中能直接编辑这些属性。我们自己定义的类没有这样的类型转换器,因此在PropertyGrid中无法编辑,需要设计自己的转换器。

  先来看一下MSDN中对TypeConverter的描述:TypeConverter类提供一种将值的类型转换为其他类型以及访问标准值和子属性的统一方法。

  继承者说明:

  从 TypeConverter 继承,以实现您自己的转换要求。当从类继承时,可以重写以下方法:

  • 若要支持自定义类型转换,请重写 CanConvertFrom、CanConvertTo、ConvertFrom 和 ConvertTo 方法。
  • 若要转换必须重新创建对象才能更改其值的类型,请重写 CreateInstance 和 GetCreateInstanceSupported 方法。
  • 若要转换支持属性 (Property) 的类型,请重写 GetProperties 和 GetPropertiesSupported 方法。如果转换的类没有属性 (Property),而您需要实现属性 (Property),则可以将 TypeConverter.SimplePropertyDescriptor 类用作实现属性 (Property) 说明符的基。当从 TypeConverter.SimplePropertyDescriptor 继承时,必须重写 GetValue 和 SetValue 方法。
  • 若要转换支持标准值的类型,请重写 GetStandardValues、GetStandardValuesExclusive、GetStandardValuesSupported 和 IsValid 方法。

三.添加转换器

  好了,了解了基本原理后,我们来为Line添加转换器。这里新建一个LineConverter类,并继承于TypeConverter。

   LineConverter

  在转换器类中,我们重写了四个函数,也就是在继承者说明中第一条的四个函数:

  ① CanConvertTo:用来说明此类可以转换为哪些类型,能转换就返回true,这里我们让他能转换为string和InstanceDescriptor,InstanceDescriptor是存储描述对象实例的信息,这些信息可用于创建对象的实例。一般转换都要实现这个转换,后面进行说明。

  ② CanConvertFrom:说明该类能有什么类型转换过来,能转换返回true,这里只对string类型进行转换。

  ③ ConvertTo:具体的转换实现,也就是提供方法将该类转换为在CanConvertTo中确定可以转换的类型。这里实现string和InstanceDescriptor的转换。

  ④ ConvertFrom:具体的转换实现,也就是提供方法将该类转换为在CanConvertFrom中确定可以转换的类型。这里实现string的转换。

  注意,每个方法处理完自己的转换后,依然要调用基类的函数来处理其他的情况。

  重写这四个方法后,给Line类型加上TypeConverter特性,在Line类定义的上面加上[TypeConverter(typeof(LineConverter))]。这样在需要转换的地方,就会调用我们所写的方法进行转换,Line属性在PropertyGrid中显示时,会调用ConvertTo,转换为字符串输出;设置属性时,会调用ConvertFrom将字符串转换为属性值,当然,字符串必须要有一定的格式,这就需要在转换的过程中进行判断,见ConvertFrom方法。

  重新生成,现在看MyControl中MyLine属性在PropertyGrid中的显示:

  自定义类在PropertyGrid上的展示方法

  可以看到MyLine属性显示出来了(以x1,y1,x2,y2的格式,我们在ConverTo方法中指定的),并可以编辑了,但必须是x1,y1,x2,y2的格式,否则会出现如下错误提示:

  自定义类在PropertyGrid上的展示方法

  这是我们在ConvertFrom方法中进行判断的。

四.编辑复杂属性的子属性

  现在可以编辑属性了,但是必须使用x1,y1,x2,y2固定的格式,不方便,而且容易出错,可不可以分别设置Line的两个点呢,可以,这里就需要编辑子属性了。Line中有Point1和Point2两个属性,我们让他们也显示出来。

  这里就是继承者说明中的第三条了。重写 GetProperties 和 GetPropertiesSupported 方法。在LineConverter中重写这两个方法:

  
自定义类在PropertyGrid上的展示方法
 1             public override bool GetPropertiesSupported(ITypeDescriptorContext context)
2 {
3 return true;
4 //return base.GetPropertiesSupported(context);
5 }
6
7 public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
8 {
9 return TypeDescriptor.GetProperties(value, attributes);
10 //return base.GetProperties(context, value, attributes);
11 }
12
自定义类在PropertyGrid上的展示方法

  重新生成,现在看MyControl中MyLine属性在PropertyGrid中的显示:

  自定义类在PropertyGrid上的展示方法

  可以看到现在我们可以编辑MyLine的子属性了。

五.属性的设计时串行化

  最后说一说ConvertTo方法中为什么要实现InstanceDescriptor的转换。Visual Studio在我们编辑控件时,会在Form1.Designer.cs文件中自动的生成代码,设置控件的属性,这叫属性对设计时序列化,如下:

  自定义类在PropertyGrid上的展示方法

  我们定义的MyLine属性,在这里并没有,如何使它出现在这里呢,只需给MyLine属性加上DesignerSerializationVisibility特性。如下

  
自定义类在PropertyGrid上的展示方法
 1 [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
2 public Line MyLine
3 {
4 get
5 {
6 return _line;
7 }
8 set
9 {
10 _line = value;
11 }
12 }
13
自定义类在PropertyGrid上的展示方法

  这里DesignerSerializationVisibility.Visible表明代码生成器生成对象的代码。DesignerSerializationVisibility.Content说明该属性在编辑时要代码生成器产生对象内容的代码,而不是对象本身的代码。DesignerSerializationVisibility.Hide代码生成器不生成对象的代码。

  重新生成,并改变控件的位置,打开Form1.Designer.cs,会发现自动生成了MyLine属性的设置值。

  自定义类在PropertyGrid上的展示方法

  这是如何生成的呢,关键就在ConvertTo方法中实现InstanceDescriptor的转换,该方法告诉代码生成器,如何去构造一个Line类型,生成时,调用Line的构造函数构造新对象初始化MyLine属性值。

六.总体的代码

  下面是完整的代码:

  
自定义类在PropertyGrid上的展示方法
  1 using System;
2 using System.ComponentModel;
3 using System.ComponentModel.Design.Serialization;
4 using System.Drawing;
5 using System.Globalization;
6 using System.Reflection;
7
8 namespace TestTypeConverter
9 {
10 //线条类
11 [TypeConverter(typeof(LineConverter))]
12 public class Line
13 {
14 // Line members.
15 Point P1;
16 Point P2;
17
18 public Point Point1
19 {
20 get
21 {
22 return P1;
23 }
24 set
25 {
26 P1 = value;
27 }
28 }
29
30 public Point Point2
31 {
32 get
33 {
34 return P2;
35 }
36 set
37 {
38 P2 = value;
39 }
40 }
41
42 public Line(Point point1, Point point2)
43 {
44 P1 = point1;
45 P2 = point2;
46 }
47 }
48
49 //转换器类
50 public class LineConverter : TypeConverter
51 {
52 public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
53 {
54 if (destinationType == typeof(string))
55 {
56 return true;
57 }
58
59 if (destinationType == typeof(InstanceDescriptor))
60 {
61 return true;
62 }
63 return base.CanConvertTo(context, destinationType);
64 }
65
66 public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
67 {
68 if (sourceType == typeof(string))
69 {
70 return true;
71 }
72
73 return base.CanConvertFrom(context, sourceType);
74 }
75
76 public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
77 {
78 if (destinationType == typeof(string) && value != null)
79 {
80 Line t = (Line)value;
81 string str = t.Point1.X + "," + t.Point1.Y + "," + t.Point2.X + "," + t.Point2.Y;
82 return str;
83 }
84
85 if (destinationType == typeof(InstanceDescriptor))
86 {
87 ConstructorInfo ci = typeof(TestTypeConverter.Line).GetConstructor(new Type[] { typeof(Point), typeof(Point) });
88 Line t = (Line)value;
89 return new InstanceDescriptor(ci, new object[] { t.Point1, t.Point2 });
90 }
91 return base.ConvertTo(context, culture, value, destinationType);
92 }
93
94 public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
95 {
96 if (value is string)
97 {
98 string str = (string)value;
99 str = str.Trim();
100 string[] v = str.Split(',');
101 if (v.Length != 4)
102 {
103 throw new NotSupportedException("Invalid parameter format");
104 }
105
106 int x1 = 0;
107 int y1 = 0;
108 int x2 = 0;
109 int y2 = 0;
110 bool res = int.TryParse(v[0], out x1);
111 if (res == false) throw new NotSupportedException("Invalid parameter format");
112 res = int.TryParse(v[1], out y1);
113 if (res == false) throw new NotSupportedException("Invalid parameter format");
114 res = int.TryParse(v[2], out x2);
115 if (res == false) throw new NotSupportedException("Invalid parameter format");
116 res = int.TryParse(v[3], out y2);
117 if (res == false) throw new NotSupportedException("Invalid parameter format");
118
119 Line line = new Line(new Point(x1, y1), new Point(x2, y2));
120 return line;
121 }
122
123 return base.ConvertFrom(context, culture, value);
124 }
125
126
127 public override bool GetPropertiesSupported(ITypeDescriptorContext context)
128 {
129 return true;
130 //return base.GetPropertiesSupported(context);
131 }
132
133 public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
134 {
135 return TypeDescriptor.GetProperties(value, attributes);
136 //return base.GetProperties(context, value, attributes);
137 }
138 }
139
140 //控件类
141 public class MyControl : System.Windows.Forms.UserControl
142 {
143 Line _line;
144
145 public MyControl()
146 {
147 _line = new TestTypeConverter.Line(
148 new Point(0, 0),
149 new Point(100, 100)
150 );
151 }
152
153 [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
154 public Line MyLine
155 {
156 get
157 {
158 return _line;
159 }
160 set
161 {
162 _line = value;
163 }
164 }
165
166 protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
167 {
168 e.Graphics.DrawLine(Pens.Red, this._line.Point1, this._line.Point2);
169 base.OnPaint(e);
170 }
171 }
172 }
自定义类在PropertyGrid上的展示方法

  新建一个Windows工程,添加该文件,在工具箱中找到我们的MyControl控件,拖入Form中,在属性框中查看控件的属性。

自定义类在PropertyGrid上的展示方法的更多相关文章

  1. Effective JavaScript Item 51 在类数组对象上重用数组方法

    Array.prototype对象上的标准方法被设计为也能够在其他对象上重用 - 即使不是继承自Array的对象. 因此,在JavaScript中存折一些类数组对象(Array-like Object ...

  2. python中自定义类对象json字符串化的方法

    1. 用 json 或者simplejson 就可以 2.定义转换函数: def convert_to_builtin_type(obj): print 'default(', repr(obj), ...

  3. 第8.16节 Python重写自定义类的__str__方法

    一. 引言 上节结合案例介绍了重写__repr__方法的关注点,重写__repr__方法的要点是要准确的输出开发人员关注的信息,并便于开发人员使用相关信息.而__str__方法是为最终用户返回类的相关 ...

  4. (转).Net中自定义类作为Dictionary的key详解

    在定义数据结构时,Dictionary提供了快速查找数据的功能,另外Dictionary< TKey, TValue >属于key-value键值对数据结构,提供了泛型的灵活性,是数据结构 ...

  5. sharepoint 2010 在自定义列表的字段上增加功能菜单

    sharepoint 2010 在自定义列表的字段上增加功能菜单方法 打开sharepoint designer 2010,找到需要修改的视图页面,例如allitem.aspx,编辑这个页面,点击高级 ...

  6. Python——dict(自定义类作key)

    Python的dict要求key为不可变数据类型,通常采用str或int,但在某些应用场景下,需要采用自定义类型对象作key, 此时的自定义类需要实现两个特殊方法:__hash__.__eq__,用于 ...

  7. PyQt学习随笔:Model&sol;View开发时从Model相关类派生自定义类需要注意的问题

    在<PyQt学习随笔:重写setData方法截获Model/View中视图数据项编辑的注意事项>介绍的方法,从Model相关类派生自定义类,通过重写setData方法以获取View中数据的 ...

  8. 实现Square类,让其继承自Rectangle类,并在Square类增添新属性和方法,在2的基础上,在Square类中重写Rectangle类中的初始化和打印方法

    实现Square类,让其继承自Rectangle类,并在Square类增添新属性和方法,在2的基础上,在Square类中重写Rectangle类中的初始化和打印方法 #import <Found ...

  9. 使用MethodType函数将方法绑定到类或实例上

    在开始正文之前,需要了解下Python的绑定方法(bound method)和非绑定方法. 简单做个测试: 定义一个类,类中由实例方法.静态方法和类方法. class ClassA: def inst ...

随机推荐

  1. 不同servlet版本的web&period;xml的头部信息

    servlet2.5 <?xml version="1.0" encoding="UTF-8"?> <web-app version=&quo ...

  2. C&comma;C&plus;&plus;经典笔试题(答案)转自:http&colon;&sol;&sol;blog&period;163&period;com&sol;jianhuali0118&commat;126&sol;blog&sol;static&sol;377499702008230104125229&sol;

    一.请填写BOOL , float, 指针变量 与“零值”比较的 if 语句.(10分) 请写出 BOOL   flag 与“零值”比较的 if 语句.(3分) 标准答案:      if ( fla ...

  3. shell小结

    一 判断 -d 测试是否为目录.-f 判断是否为文件. -s 判断文件是否为空 如果不为空 则返回0,否则返回1 -e 测试文件或目录是否存在. -r 测试当前用户是否有权限读取. -w 测试当前用户 ...

  4. 神经网络环境搭建,windows上安装theano和keras的流程

    今天碰到有朋友问道怎么在windows下安装keras,正好我刚完成搭建,总结下过程,也算是一个教程吧,给有需要的朋友. 步骤一:安装python. 这一步没啥好说的,下载相应的python安装即可, ...

  5. iOS上文本绘制的几种方法

    文本绘制在开发客户端程序中是一个比较常用的功能,可分为采用控件和直接绘制两种方式. 采用控件的方式比较简便,添加一个比如UILabel对象,然后设置相关属性就好了.但这种方式局限性也比较大. 直接绘制 ...

  6. USB Loader使用心得之游戏名称、简介、背景音乐

    我在<WAD独立安装版USB Loader的下载和安装>(链接:http://www.cnblogs.com/duxiuxing/p/4255124.html)开头提到:“任何版本的USB ...

  7. js关闭当前页面不弹出提示的方法

    js关闭当前页面不弹出提示的方法 js关闭当前页面不弹出提示的方法 "window.opener=null;window.open('','_self','');window.close() ...

  8. Linux多线程实践(三)线程的基本属性设置API

    POSIX 线程库定义了线程属性对象 pthread_attr_t ,它封装了线程的创建者能够訪问和改动的线程属性.主要包含例如以下属性: 1. 作用域(scope) 2. 栈尺寸(stack siz ...

  9. 使用QPlainText代替QText

    1.现象 在项目开发中,经常使用QText来显示解析的数据,比如从网络中获取到一个数据包,解析成中文加以显示,当时间过久或者字符串比较多的时候,就会产生一定的卡顿,所以需要限制QText的行数,或者清 ...

  10. 对spring框架的理解

    spring框架的两大核心理念就是IOC和AOP,在面试的时候经常会被问到你对spring的理解.下面大致的说一下我对spring的理解. 一.IoC 1.1.什么是IoC 众所周知,IoC就是控制反 ...