PropertyGrid--为复杂属性提供编辑功能
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
PropertyGrid--为复杂属性提供编辑功能
零.引⾔
PropertyGrid⽤来显⽰某⼀对象的属性,但是并不是所有的属性都能编辑,基本数据类型(int, double等)和.Net⼀些封装的类型(Size,Color等)可以编辑,但是对于⾃⼰定义的类型属性,是不能编辑的,本⽂主要讲述如何为⾃定义类型作为属性时,在PropertyGrid 中进⾏编辑,以及进⾏设计时序列化,本⽂主要参考MSDN,错误和不⾜之处还望指正。
⼀.⾃定义类属性
在PropertyGrid中能够编辑的都是.Net中⾃⼰封装的类,如果在⼀个类中有⼀个属性是我们⾃⼰定义的类型,在PropertyGrid中会是怎样的呢?看下⾯这个例⼦:
假如现在有⼀个类Line:
public class Line
{
Point P1;
Point P2;
public Point Point1
{
get{return P1;}
set{P1 = value;}
}
public Point Point2
{
get{return P2;}
set{P2 = value;}
}
public Line(Point point1, Point point2)
{
P1 = point1;
P2 = point2;
}
}
有⼀个控件类包含⼀个Line类型的属性:
public class MyControl : erControl
{
Line _line;
public MyControl()
{
_line = new Line(new Point(0, 0),new Point(100, 100));
}
public Line MyLine
{
get{return _line;}
set{_line = value;}
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
e.Graphics.DrawLine(Pens.Red, this._line.Point1, this._line.Point2);
base.OnPaint(e);
}
}
重新⽣成,从⼯具箱中将该控件拖⼊Form中,查看他的属性,在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。
public class LineConverter : TypeConverter
{
//该⽅法判断此类型可以转换为哪些类型
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return true;
}
if (destinationType == typeof(InstanceDescriptor))
{
return true;
}
//调⽤基类⽅法处理其他情况
return base.CanConvertTo(context, destinationType);
}
//该⽅法判断哪些类型可以转换为此类型
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
// 将该类型转换为字符串和InstanceDescriptor
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string) && value != null)
{
Line t = (Line)value;
string str = t.Point1.X + "," + t.Point1.Y + "," + t.Point2.X + "," + t.Point2.Y;
return str;
}
if (destinationType == typeof(InstanceDescriptor) && value != null)
{
ConstructorInfo ci = typeof(TestTypeConverter.Line).GetConstructor(new Type[] { typeof(Point), typeof(Point) });
Line t = (Line)value;
return new InstanceDescriptor(ci, new object[] { t.Point1, t.Point2 });
}
return base.ConvertTo(context, culture, value, destinationType);
}
//字符串和InstanceDescriptor转换为该类
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string)
{
string str = (string)value;
str = str.Trim();
string[] v = str.Split(',');
if (v.Length != 4)
{
throw new NotSupportedException("Invalid parameter format");
}
int x1 = 0;
int y1 = 0;
int x2 = 0;
int y2 = 0;
bool res = int.TryParse(v[0], out x1);
if (res == false) throw new NotSupportedException("Invalid parameter format");
res = int.TryParse(v[1], out y1);
if (res == false) throw new NotSupportedException("Invalid parameter format");
res = int.TryParse(v[2], out x2);
if (res == false) throw new NotSupportedException("Invalid parameter format");
res = int.TryParse(v[3], out y2);
if (res == false) throw new NotSupportedException("Invalid parameter format");
Line line = new Line(new Point(x1, y1), new Point(x2, y2));
return line;
}
return base.ConvertFrom(context, culture, value);
}
}
View Code
在转换器类中,我们重写了四个函数,也就是在继承者说明中第⼀条的四个函数:
①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中的显⽰:
可以看到MyLine属性显⽰出来了(以x1,y1,x2,y2的格式,我们在ConverTo⽅法中指定的),并可以编辑了,但必须是x1,y1,x2,y2的格式,否则会出现如下错误提⽰:
这是我们在ConvertFrom⽅法中进⾏判断的。
四.编辑复杂属性的⼦属性
现在可以编辑属性了,但是必须使⽤x1,y1,x2,y2固定的格式,不⽅便,⽽且容易出错,可不可以分别设置Line的两个点呢,可以,这⾥就需要编辑⼦属性了。
Line中有Point1和Point2两个属性,我们让他们也显⽰出来。
这⾥就是继承者说明中的第三条了。
重写GetProperties 和 GetPropertiesSupported ⽅法。
在LineConverter中重写这两个⽅法:
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
//return base.GetPropertiesSupported(context);
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
return TypeDescriptor.GetProperties(value, attributes);
//return base.GetProperties(context, value, attributes);
}
重新⽣成,现在看MyControl中MyLine属性在PropertyGrid中的显⽰:
可以看到现在我们可以编辑MyLine的⼦属性了。
五.属性的设计时串⾏化
最后说⼀说ConvertTo⽅法中为什么要实现InstanceDescriptor的转换。
Visual Studio在我们编辑控件时,会在Form1.Designer.cs⽂件中⾃动的⽣成代码,设置控件的属性,这叫属性对设计时序列化,如下:
我们定义的MyLine属性,在这⾥并没有,如何使它出现在这⾥呢,只需给MyLine属性加上DesignerSerializationVisibility特性。
如下
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Line MyLine
{
get
{
return _line;
}
set
{
_line = value;
}
}
这⾥DesignerSerializationVisibility.Visible表明代码⽣成器⽣成对象的代码。
DesignerSerializationVisibility.Content说明该属性在编辑时要代码⽣成器产⽣对象内容的代码,⽽不是对象本⾝的代码。
DesignerSerializationVisibility.Hide代码⽣成器不⽣成对象的代码。
重新⽣成,并改变控件的位置,打开Form1.Designer.cs,会发现⾃动⽣成了MyLine属性的设置值。
这是如何⽣成的呢,关键就在ConvertTo⽅法中实现InstanceDescriptor的转换,该⽅法告诉代码⽣成器,如何去构造⼀个Line类型,⽣成时,调⽤Line的构造函数构造新对象初始化MyLine属性值。
六.总体的代码
下⾯是完整的代码:
using System;
using ponentModel;
using ponentModel.Design.Serialization;
using System.Drawing;
using System.Globalization;
using System.Reflection;
namespace TestTypeConverter
{
//线条类
[TypeConverter(typeof(LineConverter))]
public class Line
{
// Line members.
Point P1;
Point P2;
public Point Point1
{
get
{
return P1;
}
set
{
P1 = value;
}
}
public Point Point2
{
get
{
return P2;
}
set
{
}
}
public Line(Point point1, Point point2)
{
P1 = point1;
P2 = point2;
}
}
//转换器类
public class LineConverter : TypeConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return true;
}
if (destinationType == typeof(InstanceDescriptor))
{
return true;
}
return base.CanConvertTo(context, destinationType);
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
if (destinationType == typeof(string) && value != null)
{
Line t = (Line)value;
string str = t.Point1.X + "," + t.Point1.Y + "," + t.Point2.X + "," + t.Point2.Y;
return str;
}
if (destinationType == typeof(InstanceDescriptor))
{
ConstructorInfo ci = typeof(TestTypeConverter.Line).GetConstructor(new Type[] { typeof(Point), typeof(Point) });
Line t = (Line)value;
return new InstanceDescriptor(ci, new object[] { t.Point1, t.Point2 });
}
return base.ConvertTo(context, culture, value, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string)
{
string str = (string)value;
str = str.Trim();
string[] v = str.Split(',');
if (v.Length != 4)
{
throw new NotSupportedException("Invalid parameter format");
}
int y1 = 0;
int x2 = 0;
int y2 = 0;
bool res = int.TryParse(v[0], out x1);
if (res == false) throw new NotSupportedException("Invalid parameter format");
res = int.TryParse(v[1], out y1);
if (res == false) throw new NotSupportedException("Invalid parameter format");
res = int.TryParse(v[2], out x2);
if (res == false) throw new NotSupportedException("Invalid parameter format");
res = int.TryParse(v[3], out y2);
if (res == false) throw new NotSupportedException("Invalid parameter format");
Line line = new Line(new Point(x1, y1), new Point(x2, y2));
return line;
}
return base.ConvertFrom(context, culture, value);
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
//return base.GetPropertiesSupported(context);
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) {
return TypeDescriptor.GetProperties(value, attributes);
//return base.GetProperties(context, value, attributes);
}
}
//控件类
public class MyControl : erControl
{
Line _line;
public MyControl()
{
_line = new TestTypeConverter.Line(
new Point(0, 0),
new Point(100, 100)
);
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Line MyLine
{
get
{
return _line;
}
set
{
_line = value;
}
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
e.Graphics.DrawLine(Pens.Red, this._line.Point1, this._line.Point2);
base.OnPaint(e);
}
}
}
View Code
新建⼀个Windows⼯程,添加该⽂件,在⼯具箱中找到我们的MyControl控件,拖⼊Form中,在属性框中查看控件的属性。
()。