PropertyGrid使用总结2 TypeConverter
类型转换的作用,是实现PropertyGrid输入的多个文本信息,能够与对象进行有效的转化,比如我们具有如下一个对象:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace AlbertControlExample.Controls { /// <summary> /// 定义一个新控件 /// </summary> public class AlbertControlDef : Control { public OffsetDef offsetDef { get; set; } = new OffsetDef(0,0); public AlbertControlDef() { } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); } } /// <summary> /// 定义一个左边偏移 /// </summary> public class OffsetDef{ public OffsetDef(int left, int top) { this.Left = left; this.Top = top; } public double Left { get; set; } public double Top { get; set; } } }
我们看一下显示当前的控件,会发现OffsetDef并不会显示属性,且无法编辑,如图:
这是由于系统并无法解析OffsetDef对象,意思无法将它转化为可以描述的文本集合,就不能对当前对象进行描述,那我们就需要利用TypeConverter对象,其可以定义如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace AlbertControlExample.Controls { /// <summary> /// 定义一个新控件 /// </summary> public class AlbertControlDef : Control { [TypeConverter(typeof(OffsetConverterDef))] public OffsetDef offsetDef { get; set; } = new OffsetDef(100,100); public AlbertControlDef() { } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); } } /// <summary> /// 定义一个左边偏移 /// </summary> public class OffsetDef{ public OffsetDef(int left, int top) { this.Left = left; this.Top = top; } public double Left { get; set; } public double Top { get; set; } } public class OffsetConverterDef : TypeConverter { /// <summary> /// 是否支持属性显示 /// </summary> /// <param name="context"></param> /// <returns></returns> public override bool GetPropertiesSupported(ITypeDescriptorContext context) { return true; } /// <summary> /// 返回属性文本的集合定义 /// </summary> /// <param name="context"></param> /// <param name="value"></param> /// <param name="attributes"></param> /// <returns></returns> public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) { var properties= TypeDescriptor.GetProperties(value); return properties; } } }
通过返回属性集合,系统会默认显示属性到窗体,其显示结果如下:
TypeDescriptor肯定不止这么简单,其有几个重要的函数,可以实现对象和输入框之间的互相转换,下面分别说明集合函数的功能和作用
/// <summary> /// 能否将对象转换为字符串 /// </summary> /// <param name="context"></param> /// <param name="destinationType"></param> /// <returns></returns> public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { return true; } /// <summary> /// 主要把对象转化为指定的字符串 /// </summary> /// <param name="context"></param> /// <param name="culture"></param> /// <param name="value"></param> /// <param name="destinationType"></param> /// <returns></returns> public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (value.GetType() == typeof(OffsetDef)) { OffsetDef offsetDef = value as OffsetDef; return string.Format("{0},{1}",offsetDef.Left, offsetDef.Top); } return base.ConvertTo(context, culture, value, destinationType); }
其显示会如下:
当前定义是将对象转化为字符串对象,并且显示在当前对象对应的文本框之中。以上界面,我们通过修改100,100这个文本框是无法修改的,它只能转换过来,假如我们想可以编辑他,并且自动转换为对象,可以实现如下几个函数:
/// <summary> /// 是否能从文本转化为对象 /// </summary> /// <param name="context"></param> /// <param name="sourceType"></param> /// <returns></returns> public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return true; } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value.GetType() == typeof(string)) { string objDec = value.ToString(); string[] vsp = objDec.Split(','); if (vsp.Length == 2) { OffsetDef offsetDef = new OffsetDef(int.Parse(vsp[0]),int.Parse(vsp[1])); return offsetDef; } } return base.ConvertFrom(context, culture, value); }
通过以上的定义,我们发现,现在修改100,100这个文本框的内容,对象的属性定义会自动改变,那是因为只要修改对象所对应的文本框,就会调用ConvertFrom函数,将当前文本框的内容自动转化为对象,但是不能输入错误的值,比如输入一个无法转换为int的字符串,那么就会报错。其结果显示如下:
但是其发现另外一个问题,我们修改100,100为100,200是否,那么对应的left和top的值会自动变化,但是我们修改Left/top的值的时候,并没有影响到offsetDef的值,那是因为修改left\top不会触发ConvertFrom函数,所以以上的转化就会出问题,那我们怎么解决这个问题呢,则需要实现TypeDescriptor另外两个函数:
/// <summary> /// 是否能重新创建对象,默认是不创建,当前是需要创建的,所以我们直接返回true /// </summary> /// <param name="context"></param> /// <returns></returns> public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) { return true; } /// <summary> /// 返回创建的实例对象,这个函数的调用,只要当前属性列表发生变化,当前函数都会启动调用,也会返回当前所有的属性列表 /// </summary> /// <param name="context"></param> /// <param name="propertyValues"></param> /// <returns></returns> public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) { if (propertyValues.Count == 2) { OffsetDef def = new OffsetDef(int.Parse(propertyValues["Left"].ToString()), int.Parse(propertyValues["Top"].ToString())); return def; } return base.CreateInstance(context, propertyValues); }
通过以上两个函数,你会发现,修改任何属性对应的文本,那么这个对象就会重新定义,对象也会跟着改变。这个对象不仅仅有这些功能,其还有几个非常重要的函数。
public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { return true; } public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { StandardValuesCollection standardValues = new StandardValuesCollection(new string[]{ "100,200","200,300","400,500"}); return standardValues; }
通过以上函数,可以对当前的对象指定一个标准的值,供用户下拉选择,通过以上的代码,则可以实现如下功能:
同时还有其他几个函数,这里就不一一说明,通过TypeConverter的定义,我们可以实现属性文本列表和对象的互相转换,实现对象的可配置。当前对象还有一个很重要的特性没有说明,就是attributes和CultureInfo,ITypeDescriptorContext三个对象:
- attributes对象当然是对每个Property属性上的attribute获取和进行访问的列表
- CultureInfo 主要用于实现控件的国际化的定义
- ITypeDescriptorContext 主要是当前类型标识的上下文信息,当前类型定义并没有指定TypeDescriptor对象,所以当前对象为空,我们在接下来的章节,会介绍此对象。