在设计用户控件的时候(不管是winform还是webform),对于一般的简单属性(string,int,bool……基元类型)系统会为我们自动提供相应的类型转换......
在设计用户控件的时候(不管是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(CategoryModalEditor), typeof(UITypeEditor))]
public Person Person
{
get { return person; }
set { person = value; }
}
这句话指定了系统如何打开person的属性编辑器。我们看看效果图:
用户按了confirm以后,strName和intAge属性的值就会被成功设置了。