http://blog.163.com/prince.king_521/blog/static/10689120420101019963625/
在设计用户控件的时候(不管是winform还是webform),对于一般的简单属性(string,int,bool……基元类型)系统会为我们自动提供相应的类型转换。举个例子:我们在用户控件中定义一个Age属性,
public int Age { get; set; }
这时我们打开设计器,就会看到
这个属性了。我们可以随便为其设置int类型的值。但是当我们试着填写一个string类型的字符时,系统就会弹出一个“属性无效”窗口,在这个过程中我们就可以看出系统已经为Age做了类型转换,而且这个转换失败了,因为类型不匹配。
现在让我们再写一个复杂点的属性Person。先创建一个Person类,它里面包含了name和age属性:
public class Person { private string strName; private int intAge; public string StrName { get { return strName; } set { strName = value; } } public int IntAge { get { return intAge; } set { intAge = value; } } }
然后在用户控件中,我们新建一个person的属性:
private Person person; public Person Person { get { return person; } set { person = value; } }
打开设计器窗体后我们可以看到Person属性是灰色的
为什么会这样呢?答案很简单,系统不知道如何去显示这个属性。但总得显示点儿什么吧?于是无可奈何下显示了person类的类型名。这与我们所期望的形如Location样式的可差远了
到底差在哪里呢?类型转换器!当我们在Person里面填写"zhangsan,21"的时候,类型转换器就能自动将这两个属性值对应到strName,intAge上面去,这就是类型转换器的作用。那么下面我们就来实现一个person的类型转换器。
首先新建一个新类PersonConverter,用于提供person类型转换的能力,所有的注释我都写在代码里面了,大家一看就明白:
public class PersonConverter:TypeConverter { //指示我们设置的内容能否转换 //因为我们输入的是“zhangsan,12”的字符串形式,所以这里接受字符串类型的转换 public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); } //这个方法指示了最后的这个复杂属性是以什么形式表现出来的。 //如果destinationType == typeof(string)的话,最后person显示的就是"zhangsan,21"的形式 //如果destinationType == typeof(InstanceDescriptor)的话,person显示的就是"WindowsFormsControlLibrary1.Person"类名的形式。 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); } //这个方法实现了我们输入的字符串如何被转化为person类型 public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { if (value is string) { String[] v = ((String)value).Split(','); if (v.GetLength(0) != 2) { throw new ArgumentException("Invalid parameter format"); } Person csf = new Person { Name = Convert.ToString(v[0]), Age = Convert.ToInt32(v[1]) }; return csf; } return base.ConvertFrom(context, culture, value); } //这个则是实现了具体如何显示person这个属性的方法 public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { if (destinationType == typeof(String)) { Person scope = (Person)value; String result = scope.Name.ToString() + "," + scope.Age.ToString(); return result; } if (destinationType == typeof(InstanceDescriptor)) { ConstructorInfo ci = typeof(Person).GetConstructor(new Type[] { typeof(string), typeof(Int32) }); Person scope = (Person)value; return new InstanceDescriptor(ci, new object[] { scope.Name, scope.Age }); } return base.ConvertTo(context, culture, value, destinationType); } //以下两个方法提供子属性单独设置的能力。 public override bool GetPropertiesSupported(ITypeDescriptorContext context) { return true; } public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) { return TypeDescriptor.GetProperties(typeof(Person), attributes); } }
然后在用户控件的person属性上加上这么一句,指定了person的转换器:
[TypeConverter(typeof(PersonConverter))] public Person Person { get; set; }
我们现在再来看看person的设计时的样子,这样就和我们需求的一样了。
这里再教大家一个偷懒的方法,就是实现转换器的时候不必重写最上面的那四个方法,直接重写“提供子属性单独设置的能力”的两个方法就是了。只不过这样person属性显示的效果如下:
就是person显示的内容变了,子属性还是提供设置。对于要求不高的属性也够了。
前面我们实现了形如Location属性的设计时属性设置支持,但我们有时需要的可能不只是这么复杂的属性设置。比如BackgroundImage这个属性,当我们设置它的时候,系统会弹出一个界面出来让我们选择图片:
使用这种方式无疑增加了我们选择图片的方便度。那这种形式的设置该如何实现呢?
首先我们新建一个窗体,这个窗体就是将来需要显示给用户设置属性的(比如上图的“选择资源”窗体)。我们还是以person类为例:
该窗体的后台代码如下:
private Person _person; public PersonWindow(Person person) { InitializeComponent(); Person = person; textBox1.Text = person.StrName; textBox2.Text = person.IntAge.ToString(); } public Person Person { get { return _person; } set { _person = value; } } /// <summary> /// 确认按钮 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click(object sender, EventArgs e) { Person.StrName = textBox1.Text; Person.IntAge = int.Parse(textBox2.Text); }
这里要注意的一点就是confirm按钮的DialogResult要设置为DialogResult.OK,cancel按钮的DialogResult要设置为DialogResult.Cancel。至于为什么要这么做大家往后看就知道了。
然后我们再建立一个class PersonModalEditor:System.Drawing.Design.UITypeEditor类,其代码如下:
public override System.Drawing.Design.UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context) { //指示以什么样的形式打开编辑窗体 return UITypeEditorEditStyle.Modal; } //属性编辑方法,当我们单击属性窗口中的属性按钮时会执行这个方法。 public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value) { //获取服务对象,这个对象专门为winform编辑器提供一些功能 IWindowsFormsEditorService service = (IWindowsFormsEditorService) provider.GetService(typeof (IWindowsFormsEditorService)); if (service==null) { return null; } Person p = new Person(); CategoryWindow form = new CategoryWindow(p); //这里知道为什么要设置confirm按钮的DialogResult属性的原因了吧。 if (service.ShowDialog(form) == System.Windows.Forms.DialogResult.OK) { return p; } return value; //系统会根据这个返回值在person属性里面进行填充 }
最后我们在用户控件的person属性上加上这个一句话:
[Editor(typeof( PersonModalEditor), typeof(UITypeEditor))] public Person Person { get { return person; } set { person = value; } }
这句话指定了系统如何打开person的属性编辑器。我们看看效果图:
出处: http://qianlifeng.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,否则保留追究法律责任的权利。