【Winform窗体控件开发】之五 实现类型转换器TypeConverterAttribute
这次我们讲一下关于TypeConverterAttribute属性的使用。
使用场景
前几将我们了解了如何定义窗体控件的公共属性并在设计时指定属性的值,但是我们使用的都是简单的类型(如:Int32),如果我们的控件需要绑定一个我们自定义的类,并在设计时中指定该类的值我们就需要用到TypeConverterAttribute这个属性了。
实现方法
我们知道,在设计时中我们为窗体控件指定一个值的时候都是以String型变量来显示出来的(例如:我们在第三讲时,为窗体控件属性Age赋值时,虽然我们定义的是一个Int32型的变量,但是我们可以在Designer的Attribute中为Age输入一个String类型的值)。其实,这是因为系统已经为我们实现了一个由 Int32<——>String类型的转换器(Int32Converter)。
一般情况下,.Net Framework的FCL(Framework Class Library)中已经为内建类型提供了基本的转换器(举个例子:Int32类型对应的转换器是System.ComponentModel.Int32Converter,Enum对应是System.ComponentModel.EnumConverter)。
若是自定义类型需要实现设计时支持时,我们需要自定义实现类型转换器——实现自定义转换器需要继承自TypeConverter并override相应方法(后面的代码会具体实现)。
实例一【在窗体控件中实现enum预定义列表】
我们需要在一个控件上实现一个枚举的公共属性,并在设计时中能够使用下拉列表指定枚举的值。
自定义控件代码:
/*
实现在属性浏览器中提供标准值下拉列表的简单类型转换器
1.定义一个从 TypeConverter 派生的类。
2.重写 GetStandardValuesSupported 方法并返回 true。
3.重写 GetStandardValues 方法并返回包含属性类型标准值的 TypeConverter.StandardValuesCollection。属性的标准值必须与属性自身的类型一致。
4.重写 CanConvertFrom 方法并为类型字符串的 sourceType 参数值返回 true。
5.重写 ConvertFrom 方法并基于 value 参数返回相应的属性值。
6.将指示类型转换器类型的 TypeConverterAttribute 应用于要为其提供一组标准值的类型。
*/
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace WinFormControlLibrary
{
public partial class FourthControl自定义控件属性实现类型转换器之标准值下拉列表的类型转换器一 : Control
{
private MoneyType _moneyType;
[Browsable(true)]
public MoneyType MoneyType
{
get { return _moneyType; }
set { _moneyType = value; }
}
public FourthControl自定义控件属性实现类型转换器之标准值下拉列表的类型转换器一()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
#region 重绘控件
Graphics g = pe.Graphics;
#region 边框
//
// 设定大小区域
//
Size size = new Size(Size.Width - 1, Size.Height - 1);
//
// 设定Control 的大小
//
Rectangle rectagle = new Rectangle(Point.Empty, size);
// 绘制控件边框
g.DrawRectangle(Pens.Black, rectagle);
#endregion
#region 文本内容
g.DrawString(
"自定义控件属性实现类型转换器之标准值下拉列表的类型转换器一",
Font,
Brushes.Black,
new PointF(1, 1)
);
#endregion
#endregion
}
}
#region Int32、String、枚举类型和其他类型具有默认的类型转换器。
/// <summary>
/// 因此,不用为基本类型实现TypeConverter转换器
/// </summary>
[TypeConverter(typeof(EnumConverter))] // 为对象指出使用转换器
public enum MoneyType
{
人民币,
美元,
日元,
法郎,
英镑
}
#endregion
}
效果:
仔细观察代码你会发现,实现这个效果我们并没有增加新的额外代码(只是在MoneyType对象上增加了[TypeConverter(typeof(EnumConverter))]属性),这就是我们上面说的enum类型使用了.Net Framework 自己定义的转换器EnumConverter
实例二【实现自定义类型的预定义标准值列表】
/*
实现在属性浏览器中提供标准值下拉列表的简单类型转换器
1.定义一个从 TypeConverter 派生的类。
2.重写 GetStandardValuesSupported 方法并返回 true。
3.重写 GetStandardValues 方法并返回包含属性类型标准值的 TypeConverter..::.StandardValuesCollection。属性的标准值必须与属性自身的类型一致。
4.重写 CanConvertFrom 方法并为类型字符串的 sourceType 参数值返回 true。
5.重写 ConvertFrom 方法并基于 value 参数返回相应的属性值。
6.将指示类型转换器类型的 TypeConverterAttribute 应用于要为其提供一组标准值的类型。
*/
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Collections;
using System.Globalization;
namespace WinFormControlLibrary
{
public partial class FourthControl自定义控件属性实现类型转换器之标准值下拉列表的类型转换器二 : Control
{
private int _friendsNum = 0;
[TypeConverter(typeof(StandardValuesIntConverter))]
public int FriendsNum
{
get
{
return this._friendsNum;
}
set
{
if (value.GetType() == typeof(int))
this._friendsNum = value;
}
}
public FourthControl自定义控件属性实现类型转换器之标准值下拉列表的类型转换器二()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
#region 重绘控件
Graphics g = pe.Graphics;
#region 边框
//
// 设定大小区域
//
Size size = new Size(Size.Width - 1, Size.Height - 1);
//
// 设定Control 的大小
//
Rectangle rectagle = new Rectangle(Point.Empty, size);
// 绘制控件边框
g.DrawRectangle(Pens.Black, rectagle);
#endregion
#region 文本内容
g.DrawString(
"自定义控件属性实现类型转换器之标准值下拉列表的类型转换器二",
Font,
Brushes.Black,
new PointF(1, 1)
);
#endregion
#endregion
}
}
public class StandardValuesIntConverter : TypeConverter
{
private ArrayList arrValues;
public StandardValuesIntConverter()
{
// Initializes the standard values list with defaults.
arrValues = new ArrayList(new int[] { 1, 2, 3, 4, 5 });
}
// 是否支持标准值
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
// 获取标准值集合
public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
StandardValuesCollection svc = new StandardValuesCollection(arrValues);
return svc;
}
// 是否可以转换
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
else
{
return base.CanConvertFrom(context, sourceType);
}
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value.GetType() == typeof(string))
{
// 转换到整型
int newVal = int.Parse((string)value);
if (!arrValues.Contains(newVal))
{
arrValues.Add(newVal);
arrValues.Sort();
}
return newVal;
}
else
{
return base.ConvertFrom(context, culture, value);
}
}
}
}
与enum类型不同,在这个窗体控件中我们使用了ArrayList对象保存我们预定义好的值,但是系统没有提供我们ArrayList对应的转换器,因此我们实现了一个自定义对象类型转换器StandardValuesIntConverter用来在设计时和代码之间进行对应。
注意:
在实例一和实例二我们声明的类型转化器位置不一样,实例一中是在自定义类上声明的,实例二中是在窗体控件的属性(自定义类)上声明的,但是效果一样。
【实例三】实例二中已经讲到如何对自定义类型的对象实现类型转换器,接下来我们再看一个实例,该实例与实例二比较相似,但是我们定义了一个Money的自定义类型用于窗体控件的属性,我们希望在设计时的ccMoney属性中输入一个特定格式的字符串(例如:123,456)来定义Money实例的值,123对应Min属性,456对应Max属性。
具体操作如下
money类型定义如下:
/// <summary>
/// 自定义对象
/// </summary>
public class Money
{
private Int32 _min;
private Int32 _max;
[Browsable(true)]
public Int32 Min
{
get { return _min; }
set { _min = value; }
}
[Browsable(true)]
public Int32 Max
{
get { return _max; }
set { _max = value; }
}
public Money()
{
}
public Money(Int32 min, Int32 max)
{
_min = min;
_max = max;
}
}
该对象类中有两个属性,我们需要在设计时中能够对窗体控件中的这两个属性进行设置。
实现方法如下
窗体控件代码:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel.Design.Serialization;
using System.Reflection;
namespace WinFormControlLibrary
{
public partial class FourthControl自定义控件属性实现类型转换器之自定义类型 : Control
{
private String _ccMoney;
[Browsable(true)]
public String ccMoney
{
get
{
return _ccMoney;
}
set
{
_ccMoney = value;
}
}
public FourthControl自定义控件属性实现类型转换器之自定义类型()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
#region 重绘控件
Graphics g = pe.Graphics;
#region 边框
//
// 设定大小区域
//
Size size = new Size(Size.Width - 1, Size.Height - 1);
//
// 设定Control 的大小
//
Rectangle rectagle = new Rectangle(Point.Empty, size);
// 绘制控件边框
g.DrawRectangle(Pens.Black, rectagle);
#endregion
#region 文本内容
g.DrawString("自定义控件属性实现类型转换器之自定义类型", Font, Brushes.Black, new PointF(1, 1));
#endregion
#endregion
}
}
#region 自定义类型 和 自定义类型转换器
/// <summary>
/// 自定义对象
/// </summary>
[TypeConverter(typeof(MoneyConverter))] // 为对象指出使用自定义转换器
public class Money
{
private Int32 _min;
private Int32 _max;
[Browsable(true)]
public Int32 Min
{
get { return _min; }
set { _min = value; }
}
[Browsable(true)]
public Int32 Max
{
get { return _max; }
set { _max = value; }
}
public Money()
{
}
public Money(Int32 min, Int32 max)
{
_min = min;
_max = max;
}
}
/// <summary>
/// Money对象的转换器
/// </summary>
public class MoneyConverter : TypeConverter
{
/// <summary>
/// 是否可从指定类型转换到本类型(Money)
/// </summary>
/// <param name="context">提供组件的上下文信息,例如 组件的容器和属性描述</param>
/// <param name="sourceType">指定对象的类型</param>
/// <returns>能够转换返回ture 否则返回false</returns>
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(String))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <summary>
/// 从指定类型转换到本类型(Money)
/// </summary>
/// <param name="context">提供组件的上下文信息,例如 组件的容器和属性描述</param>
/// <param name="culture">本地化对象</param>
/// <param name="value">欲转换对象</param>
/// <returns>返回转换对象</returns>
public override object ConvertFrom(
ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value
)
{
if (value is String)
{
String[] strArray = ((String)value).Split(',');
// 数组的长度为2时
if (strArray.GetLength(0) != 2)
{
throw new ArgumentException("Invalid paremeter format");
}
Money money = new Money();
money.Min = Convert.ToInt32(strArray[0]);
money.Max = Convert.ToInt32(strArray[1]);
return money;
}
return base.ConvertFrom(context, culture, value);
}
/// <summary>
/// 是否可以转换到指定类型
/// </summary>
/// <param name="context">提供组件的上下文信息,例如 组件的容器和属性描述</param>
/// <param name="destinationType">指定装换对象的类型</param>
/// <returns>能够转换返回ture 否则返回false</returns>
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);
}
/// <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,
System.Globalization.CultureInfo culture,
object value,
Type destinationType
)
{
String result = "";
if (destinationType == typeof(String))
{
Money money = (Money)value;
result = String.Format("{0},{1}", money.Min, money.Max);
return result;
}
// 如果对象类型为InstanceDescriptor
if (destinationType == typeof(InstanceDescriptor))
{
#region 使用反射构造指定类型对象的实例
Money money = (Money)value;
//
// 获取构造函数信息的对象
//
ConstructorInfo ci = typeof(Money).GetConstructor(new Type[] { typeof(Int32), typeof(Int32) });
//
// 返回可序列化的对象【TypeDescriptor 的若干方法使用 InstanceDescriptor 来表示或实例化对象】
//
return new InstanceDescriptor(ci, new object[] { money.Min, money.Max });
#endregion
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
#endregion
}
注意在ConvertForm方法中我们将设计时属性中输入的字符串解析为Money对象。
效果图:
在设计时给属性赋值的时只需输入 格式为"min,max"格式的字符串就可以对Money对象的ccMoney窗体属性赋值了。
在【实例三中】有没有发现一个问题,我对ccMoney的设置是默认规定了一个字符串格式("min,max" ),这无论是对于使用该控件的人和程序开发人员来说都是不好的设计方法。对于Money这种复杂的对象属性来说最好自己定义一个UIEditer来为单独的子属性提供编辑功能。
好了说了这么多,类型转换器的内容基本上就这点东西了。下次我们说说如何为属性的子属性提供编辑功能。:)