上一篇文章我已经介绍了TypeConverterAttribute 元数据的作用,本文将通过代码向你展示具体的实现。在这个例子中,我要给控件添加一个复杂的属性,这个属性对这个控件没有什么功用,纯粹是为了演示,有些牵强附会了。
现在在前一篇文章中的创建的控件代码中添加一个 Scope 属性:
[Browsable(
true
)]
public
Scope Scope
{
get
{
return _scope;
}
set
{
_scope = value;
}
}
public
class
Scope
{
private Int32 _min;
private Int32 _max;
public Scope()
{
}
public Scope(Int32 min, Int32 max)
{
_min = min;
_max = max;
}
[Browsable( true )]
public Int32 Min
{
get
{
return _min;
}
set
{
_min = value;
}
}
[Browsable( true )]
public Int32 Max
{
get
{
return _max;
}
set
{
_max = value;
}
}
}
添加完属性后, build 控件工程,然后在测试的工程里选中添加的控件,然后在属性浏览器里观察它的属性,发现 Scope 属性是灰的,不能编辑。前一篇文章提到了,在属性浏览器里可以编辑的属性都是有类型转换器的,而 .NET 框架为基本的类型和常用的类型都提供了默认的类型转换器。接下来我们为 Scope 类添加一个类型转换器,以便这个属性能够被编辑,而且也可以在源代码文件里自动生成相应的代码。下面是类型转换器的代码:
public
class
ScopeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof (String)) return true ;
return base .CanConvertFrom(context, sourceType);
}
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 object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
String result = "" ;
if (destinationType == typeof (String))
{
Scope scope = (Scope)value;
result = scope.Min.ToString() + " , " + scope.Max.ToString();
return result;
}
if (destinationType == typeof (InstanceDescriptor))
{
ConstructorInfo ci = typeof (Scope).GetConstructor( new Type[] { typeof (Int32), typeof (Int32) } );
Scope scope = (Scope)value;
return new InstanceDescriptor(ci, new object [] { scope.Min,scope.Max } );
}
return base .ConvertTo(context, culture, value, destinationType);
}
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 " );
}
Scope csf = new Scope();
csf.Min = Convert.ToInt32(v[ 0 ]);
csf.Max = Convert.ToInt32(v[ 1 ]);
return csf;
}
return base .ConvertFrom(context, culture, value);
}
}
现在我们为类型提供类型转换器,我们在类型前面添加一个 TypeConverterAttribute ,如下:
[TypeConverter(
typeof
(ScopeConverter))]
public
class
Scope
我们修改默认的值,然后看看 Form 设计器为我们生成了什么代码:
this
.myListControl1.BackColor
=
System.Drawing.SystemColors.ActiveCaptionText;
this
.myListControl1.Item.Add(
1
);
this
.myListControl1.Item.Add(
2
);
this
.myListControl1.Item.Add(
3
);
this
.myListControl1.Item.Add(
6
);
this
.myListControl1.Item.Add(
8
);
this
.myListControl1.Item.Add(
9
);
this
.myListControl1.Location
=
new
System.Drawing.Point(
12
,
34
);
this
.myListControl1.Name
=
"
myListControl1
"
;
this
.myListControl1.Scope
=
new
CustomControlSample.Scope(
10
,
200
);
this
.myListControl1.Size
=
new
System.Drawing.Size(
220
,
180
);
this
.myListControl1.TabIndex
=
1
;
this
.myListControl1.Text
=
"
myListControl1
"
;
在上一篇文章,我为控件添加一个一个复杂属性,并且为这个属性的类型的编写了一个类型转换器,现在我们来看看这个类型转换器的代码,并解释一下这些代码的意义。
要实现一个类型转换器,我们必须要重写( override )四个方法:
CanConvertFrom ()―― 根据类型参数进行测试,判断是否能从这个类型转换成当前类型 , 在本例中我们只提供转换 string 和 InstanceDescriptor 类型的能力 。
CanConvertTo ()―― 根据类型参数进行测试,判断是否能从当前类型转换成指定的类型。
ConvertTo() ―― 将参数 value 的值转换为指定的类型。
ConvertFrom ()―― 串换参数 value ,并返回但书类型的一个对象。
public
override
object
ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture,
object
value, Type destinationType)
{
String result = "" ;
if (destinationType == typeof (String))
{
Scope scope = (Scope)value;
result = scope.Min.ToString() + " , " + scope.Max.ToString();
return result;
}
if (destinationType == typeof (InstanceDescriptor))
{
ConstructorInfo ci = typeof (Scope).GetConstructor( new Type[] { typeof (Int32), typeof (Int32) } );
Scope scope = (Scope)value;
return new InstanceDescriptor(ci, new object [] { scope.Min,scope.Max } );
}
return base .ConvertTo(context, culture, value, destinationType);
}
上面是 ConvertTo 的实现,如果转换的目标类型是 string ,我将 Scope 的两个属性转换成 string 类型,并且用一个“,”连接起来,这就是我们在属性浏览器里看到的表现形式,如图:
如果转换的目标类型是实例描述器( InstanceDescriptor ,它负责生成实例化的代码),我们需要构造一个实例描述器,构造实例描述器的时候,我们要利用反射机制获得 Scope 类的构造器信息,并在 new 的时候传入 Scope 实例的两个属性值。实例描述器会为我们生成这样的代码: this.myListControl1.Scope = new CustomControlSample.Scope(10, 200) ;在最后不要忘记调用 base.ConvertTo(context, culture, value, destinationType) ,你不需要处理的转换类型,交给基类去做好了。
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 " );
}
Scope csf = new Scope();
csf.Min = Convert.ToInt32(v[ 0 ]);
csf.Max = Convert.ToInt32(v[ 1 ]);
return csf;
}
return base .ConvertFrom(context, culture, value);
}
}
上面是 ConvertFrom 的代码,由于系统能够直接将实例描述器转换为 Scope 类型,所以我们就没有必要再写代码,我们只需要关注如何将 String (在属性浏览出现的属性值的表达)类型的值转换为 Scope 类型。没有很复杂的转换,只是将这个字符串以“,”分拆开,并串换为 Int32 类型,然后 new 一个 Scope 类的实例,将分拆后转换的两个整型值赋给 Scope 的实例,然后返回实例。在这段代码里,我们要判断一下用户设定的属性值是否有效。比如,如果用户在 Scope 属性那里输入了“ 10200 ” ,由于没有输入“,”,我们无法将属性的值分拆为两个字符串,也就无法进行下面的转换,所以,我们要抛出一个异常,通知用户重新输入。
前面的几篇文章中,我们给控件添加一个复杂的类型Scope ,并且给它的类型提供的一个类型转换器,现在我们可以在属性浏览器中编辑它的值,并且它的值也被串行化的源代码里了。但是你有没有发现,在属性浏览器里编辑这个属性的值还是不太方便。因为属性只是“ 10 , 200 ” 这种形式的,所以,你必须按照这种格式来修改,一旦格式错误就会引发异常,比如输入一个“ 10200 ” 。我们期望这个属性的每一子属性都能够被独立的编辑就好了,这并非不能实现,而且实现还很简单。
为了在属性浏览器里能够独立的编辑子属性,我们还要重写两个方法: GetPropertiesSupported ()和 GetProperties ();下面是 ScopeConverter 的完整代码:
public
class
ScopeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof (String)) return true ;
return base .CanConvertFrom(context, sourceType);
}
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 object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
String result = "" ;
if (destinationType == typeof (String))
{
Scope scope = (Scope)value;
result = scope.Min.ToString() + " , " + scope.Max.ToString();
return result;
}
if (destinationType == typeof (InstanceDescriptor))
{
ConstructorInfo ci = typeof (Scope).GetConstructor( new Type[] { typeof (Int32), typeof (Int32) } );
Scope scope = (Scope)value;
return new InstanceDescriptor(ci, new object [] { scope.Min,scope.Max } );
}
return base .ConvertTo(context, culture, value, destinationType);
}
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 " );
}
Scope csf = new Scope();
csf.Min = Convert.ToInt32(v[ 0 ]);
csf.Max = Convert.ToInt32(v[ 1 ]);
return csf;
}
return base .ConvertFrom(context, culture, value);
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true ;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
return TypeDescriptor.GetProperties( typeof (Scope), attributes);
}
}
在 GetProperties 方法里,我用 TypeDescriptor 获得了 Scope 类的所有的属性描述器并返回。如果你对 TypeDescriptor 还不熟悉的话,可以参考 MSDN 。
重写这两个方法并编译以后,在测试工程里查看控件的属性,你可以看到 Scope 是如下的形式:
前几篇文章我们一直在讨论如何更方便的编辑复杂类型的属性,在这个过程中我介绍了类型转换器以及如何制作自己的类型转换器来实现属性值的串行化和实现子属性的编辑。对于Scope 这种级别的复杂属性,一个类型转换器就已经足够了,但是对于更为复杂的属性,单单使用类型转换器已经不足以应付了,比如我们常用的 Font 属性。
在这种情况下,我们就需要提供更为复杂的编辑方式,比如属性编辑对话框,你还记得 Font 对话框吗?现在我们就来看看如何实现更复杂的属性编辑。复杂的属性编辑器分为两种类型,一种是弹出式模态对话框属性编辑器,一种式下拉式属性编辑器。如果你还没有感性的认识的话,可以观察一下 TextBox 控件的属性, Font 属性的编辑器是模态对话框属性编辑器, Dock 属性的编辑器是下拉式属性编辑器。
接下来我们来制作一个模态对话框编辑器,虽然 Scope 属性并不复杂,但是为了演示的方便,我们还是用它来做例子。
首先我们要做一个用来编辑属性的对话框,在对话框的构造函数里传入要编辑的属性的值。在对话框类里,声明一个 Scope 类型的私有变量 _scope 用以保存传入和编辑后的值。还要增加一个 Scope 属性,以便外部环境能够获取编辑后的结果。对话框的外观如下:
在这个对话框里,我们要把 OK 按钮的 DialogResult 属性设为 OK (当点击 OK 按钮时,模态对话框关闭,并返回 DialogResult.OK ),将 Cancel 按钮的 DialogResult 属性设为 Cancel( 当点击 OK 按钮时,模态对话框关闭,并返回 DialogResult.OK) 。另外我们要对用户输入的值做验证,以保证 Scope 的 min 和 max 值都是 Int32 类型。下边是对话框的代码:
我们在 Scope 属性前加上了 [Editor(typeof(ScopeEditor),typeof(UITypeEditor))] 元数据。 Build 工程,查看实际的效果。在测试工程的窗体上,选中控件,观察 Scope 属性,当我们单击 Scope 属性的值时,在属性值的后边出现了一个按钮,如图:
using
System;
using
System.Collections.Generic;
using
System.ComponentModel;
using
System.Data;
using
System.Drawing;
using
System.Text;
using
System.Windows.Forms;
namespace
CustomControlSample
{
public partial class ScopeEditorDialog : Form
{
private Scope _scope = null ;
public ScopeEditorDialog(Scope scope)
{
InitializeComponent();
_scope = scope;
textBox1.Text = _scope.Min.ToString();
textBox2.Text = _scope.Max.ToString();
}
private void button1_Click( object sender, EventArgs e)
{
_scope.Min = Convert.ToInt32(textBox1.Text);
_scope.Max = Convert.ToInt32(textBox2.Text);
}
private void textBox1_Validating( object sender, CancelEventArgs e)
{
try
{
Int32.Parse(textBox1.Text);
}
catch (FormatException)
{
e.Cancel = true ;
MessageBox.Show( " 无效的值 " , " 验证错误 " , MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void textBox2_Validating( object sender, CancelEventArgs e)
{
try
{
Int32.Parse(textBox2.Text);
}
catch (FormatException)
{
e.Cancel = true ;
MessageBox.Show( " 无效的值 " , " 验证错误 " , MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
public Scope Scope
{
get
{
return _scope;
}
set
{
_scope = value;
}
}
}
}
每一个属性的编辑器都是直接或者间接的派生于 UITypeEditor 。开发环境从来也不会直接调用我们编写的模态对话框来编辑属性,而是调用 UITypeEditor 的某些虚方法,所以我们还必须提供一个派生于 UITypeEditor 的类来与开发环境通信。下边的代码实现了 Scope 的编辑器:
using
System;
using
System.ComponentModel;
using
System.Drawing.Design;
using
System.Windows.Forms.Design;
using
System.Windows.Forms;
namespace
CustomControlSample
{
public class ScopeEditor:UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
if (context != null && context.Instance != null )
{
return UITypeEditorEditStyle.Modal;
}
return base .GetEditStyle(context);
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
IWindowsFormsEditorService editorService = null ;
if (context != null && context.Instance != null && provider != null )
{
editorService = (IWindowsFormsEditorService)provider.GetService( typeof (IWindowsFormsEditorService));
if (editorService != null )
{
MyListControl control = (MyListControl)context.Instance;
ScopeEditorDialog dlg = new ScopeEditorDialog(control.Scope);
if (dlg.ShowDialog() == DialogResult.OK)
{
value = dlg.Scope;
return value;
}
}
}
return value;
}
}
}
在这个类里,我们重写了两个方法,一个是 GetEditStyle ,在这个方法里,我们通知开发环境,属性的编辑器是一个模态对话框。另一个方法是 EditValue ,这是最核心的方法,在这个方法里,我们通过上下文环境获得了正在编辑的控件的实例,并将实例的 Scope 属性传递给属性编辑对话框,显示对话框供用户编辑属性的值,用户编辑完属性的值,并关闭对话框,这时,我们从对话框里获取编辑后的结果反会给开发环境。 编写完 Editor ,我们就要将它应用到 MyListControl 的 Scope 属性上 , 现在的 Scope 属性定义如下 :
[Browsable(
true
)]
[Editor(
typeof
(ScopeEditor),
typeof
(UITypeEditor))]
public
Scope Scope
{
get
{
return _scope;
}
set
{
_scope = value;
}
}
当我们点击这个按钮后,弹出了属性编辑的对话框,如图:
我们在对话框里编辑属性的值,并点击 OK 关闭对话框,现在 Scope 属性值已经被修改了。
本文详细介绍如何为自定义控件的复杂属性实现类型转换器及属性编辑器,包括模态对话框编辑器的开发过程。
430

被折叠的 条评论
为什么被折叠?



