惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
爱范儿
爱范儿
H
Help Net Security
V
Visual Studio Blog
J
Java Code Geeks
Stack Overflow Blog
Stack Overflow Blog
Microsoft Security Blog
Microsoft Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
MyScale Blog
MyScale Blog
The Cloudflare Blog
Martin Fowler
Martin Fowler
D
Docker
腾讯CDC
F
Fortinet All Blogs
雷峰网
雷峰网
GbyAI
GbyAI
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Blog — PlanetScale
Blog — PlanetScale
Engineering at Meta
Engineering at Meta
博客园 - 聂微东
博客园 - 叶小钗

博客园 - bobbychen

C++之不带指针类的设计——Boolean .ctor,.cctor 以及 对象的构造过程 C#中virtual 方法和abstract方法的区别 SQL循环插入批量数据 【VS.NET 调试异常】 CLR 无法从 COM 上下文 XXX 转换为 COM 上下文 XXX SQL重复记录查询 String.Format格式说明 C#数字格式化方法全面解析 HTML特殊字符 - 补遗 【Access】 LEFT OUTER JOIN 关联多表的查询语句 【转帖】C#与C Windows API数据类型对应关系 Visual Studio 进行单元测试时如何附加被测试文件的方法总结 【转帖】C# 与 C++ 数据类型对照 【.Net Compact Framework开发】 使用 Visual Studio 对移动项目进行Unit Testing的方法总结 【转帖】const 与 readonly 的区别 【转帖】解决继承窗体或用户控件时“visual继承当前被禁用,因为基类引用设备特定的组件或包含 p/invoke”问题 PowerDesigner实体模型CDM中关于建立Entity之间关系的备忘 【部署】Visual Studio 2008 打包部署.Net Framework 2.0 应用程序提示需要安装.Net Framework 3.5的解决方法 SQL 使用CONVERT函数 格式化日期
【Winform窗体控件开发】之五 实现类型转换器TypeConverterAt...
bobbychen · 2010-12-08 · via 博客园 - bobbychen

  这次我们讲一下关于TypeConverterAttribute属性的使用。

使用场景

  前几将我们了解了如何定义窗体控件的公共属性并在设计时指定属性的值,但是我们使用的都是简单的类型(如:Int32),如果我们的控件需要绑定一个我们自定义的类,并在设计时中指定该类的值我们就需要用到TypeConverterAttribute这个属性了。

实现方法

  我们知道,在设计时中我们为窗体控件指定一个值的时候都是以String型变量来显示出来的(例如:我们在第三讲时,为窗体控件属性Age赋值时,虽然我们定义的是一个Int32型的变量,但是我们可以在Designer的Attribute中为Age输入一个String类型的值)。其实,这是因为系统已经为我们实现了一个由 Int32<——>String类型的转换器(Int32Converter)。

  一般情况下,.Net Framework的FCL(Framework Class Library)中已经为内建类型提供了基本的转换器(举个例子:Int32类型对应的转换器是System.ComponentModel.Int32ConverterEnum对应是System.ComponentModel.EnumConverter)。

  若是自定义类型需要实现设计时支持时,我们需要自定义实现类型转换器——实现自定义转换器需要继承自TypeConverteroverride相应方法(后面的代码会具体实现)。

实例一【在窗体控件中实现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
}

效果:

image

仔细观察代码你会发现,实现这个效果我们并没有增加新的额外代码(只是在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);
}
}
}
}

image

与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对象。

效果图:

image

在设计时给属性赋值的时只需输入 格式为"min,max"格式的字符串就可以对Money对象的ccMoney窗体属性赋值了。

在【实例三中】有没有发现一个问题,我对ccMoney的设置是默认规定了一个字符串格式("min,max" ),这无论是对于使用该控件的人和程序开发人员来说都是不好的设计方法。对于Money这种复杂的对象属性来说最好自己定义一个UIEditer来为单独的子属性提供编辑功能。

好了说了这么多,类型转换器的内容基本上就这点东西了。下次我们说说如何为属性的子属性提供编辑功能。:)