【Visual Studio风格开发系列 - PropertyGrid控件】深度解析 TypeConverter & TypeConverterAttribute

前言
    我们在开发复杂控件的时候不可避免的碰到类型转换TypeConverter,微软给我们提供了很多转换类如ArrayConverter,BaseNumberConverter,BooleanConverter(MSDN上更多:ms-help://MS.MSDNQTR.v80.en/MS.MSDN.v80/MS.NETDEVFX.v20.en/cpref3/html/N_System_ComponentModel.htm)等直接或间接的继承了TypeConverter类。我们在类型转换的时候经常用到这些类。然而我们如何编写自定义的TypeConverter类呢,又怎么样在复杂控件中使用呢。

TypeConverter Class
    TypeConverter类就是将一种类型(object,可以说是任何类型)转换到另一种类型(一般为string),或者将另一种类型转换回来。所有继承TypeConverter类型的都必须实现4个方法:(这里以另一种类型string为例)
     CanConverterTo 有两个重载方法,
             TypeConverter.CanConvertTo (Type)
             TypeConverter.CanConvertTo (ITypeDescriptorContext, Type)
       都有一个Type参数(要转换成什么类型),例如我们设计的要转换成string,在方法体里面判断这个参数的Type如果是string,则返回true,否则返回           false
     ConverterTo 也有两重载,
             TypeConverter.ConvertTo (Object, Type)
             TypeConverter.ConvertTo (ITypeDescriptorContext, CultureInfo, Object, Type)
       都有Object和Type参数,将Object转成Type类型的Object,返回Type类型的Object。
  下面类似的两个方法,不过方向相反,是从其他类型装换回来。
     CanConverterFrom 重载,
             TypeConverter.CanConvertFrom (Type)
             TypeConverter.CanConvertFrom (ITypeDescriptorContext, Type)
       在方法体里面判断参数Type是不是能转换回来的类型,例如string类型,如果是返回true,否则返回false。
     ConverterFrom 重载,
            TypeConverter.ConvertFrom (Object)
            TypeConverter.ConvertFrom (ITypeDescriptorContext, CultureInfo, Object)
       在方法体里面判断参数Object的类型是不是能转换回来的类型,例如string类型,如果是返回转换回来的类型。
      
    举例说明,以GPS经纬度位置为例,经纬度位置GPSLocation包括复杂属性经度Longitude和纬度Latitude。现我们根据其一属性Longitude类写个LongtitudeTypeConverter类。
    首先我们得有个Longtitude类吧。

 

public class Longitude { private int _Degrees; private int _Minutes; private int _Seconds; private LongitudeDirection _Direction; /// <summary> /// 度数 /// </summary> public int Degrees { } /// <summary> /// 分度 /// </summary> public int Minutes { } /// <summary> /// 秒读 /// </summary> public int Seconds { } /// <summary> /// 方向 /// </summary> public LongitudeDirection Direction { } }

有了个这个类,我们怎样将其转换到string类或其他类呢(这里假设string类)例如“24W3'4”形式,也许你会说重写ToString()方法不就行了,似乎可行,但如果转换成其他类呢,又从其他类转换回来呢,怎么办。还有在复杂控件中Designer设计中又该怎么办。(在复杂控件的应用稍后介绍)
    自然,这样我们是不是要写个转换类比较好呢,这个类必须直接或这间接继承TypeConverter类。

class LongitudeTypeConverter : TypeConverter { //...... }

然后重载实现上面说的四个方法,现在我要Longitude类转换到string类型

public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) return true; else return base.CanConvertFrom(context, sourceType); } public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if ((destinationType == typeof(string)) | (destinationType == typeof(InstanceDescriptor))) return true; else return base.CanConvertTo(context, destinationType); } public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { // check that the value we got passed on is of type Longitude if (value != null) if (!(value is Longitude)) throw new Exception(WrongType); // convert to a string if (destinationType == typeof(string)) { // no value so we return an empty string if (value == null) return string.Empty; // strongly typed Longitude LongValue = value as Longitude; // get the two type converters to use TypeConverter IntConverter = TypeDescriptor.GetConverter(typeof(int)); TypeConverter EnumConverter = TypeDescriptor.GetConverter(typeof(LongitudeDirection)); // convert to a string and return return IntConverter.ConvertToString(context, culture, LongValue.Degrees) + EnumConverter.ConvertToString(context, culture, LongValue.Direction).Substring(0, 1) + IntConverter.ConvertToString(context, culture, LongValue.Minutes) + MinutesUnit + IntConverter.ConvertToString(context, culture, LongValue.Seconds) + SecondsUnit; } // convert to a instance descriptor if (destinationType == typeof(InstanceDescriptor)) { // no value so we return no instance descriptor if (value == null) return null; // strongly typed Longitude LongValue = value as Longitude; // used to descripe the constructor MemberInfo Member = null; object[] Arguments = null; // get the constructor for the type Member = typeof(Longitude).GetConstructor(new Type[] { typeof(int), typeof(int), typeof(int), typeof(LongitudeDirection) }); // create the arguments to pass along Arguments = new object[] { LongValue.Degrees, LongValue.Minutes, LongValue.Seconds, LongValue.Direction }; // return the instance descriptor if (Member != null) return new InstanceDescriptor(Member, Arguments); else return null; } // call the base converter return base.ConvertTo(context, culture, value, destinationType); } public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { // no value so we return a new Longitude instance if (value == null) return new Longitude(); // convert from a string if (value is string) { // get strongly typed value string StringValue = value as string; // empty string so we return a new Longitude instance if (StringValue.Length <= 0) return new Longitude(); // get the position of the West longitude separator int DirectionPos = StringValue.IndexOf(LongitudeDirection.West.ToString().Substring(0, 1)); LongitudeDirection Direction = LongitudeDirection.West; // if not found get the position of the East longitude separator if (DirectionPos == -1) { DirectionPos = StringValue.IndexOf(LongitudeDirection.East.ToString().Substring(0, 1)); Direction = LongitudeDirection.East; } // get the minutes and seconds characters int MinutesPos = StringValue.IndexOf(MinutesUnit); int SecondsPos = StringValue.IndexOf(SecondsUnit); // no minutes present if (MinutesPos == -1) throw new Exception(MinutesMissing); // no seconds present if (SecondsPos == -1) throw new Exception(SecondsMissing); // no minutes present if (DirectionPos == -1) throw new Exception(DirectionMissing); // no degrees present if (DirectionPos == 0) throw new Exception(DegreesMissing); // get the type converters we need TypeConverter IntConverter = TypeDescriptor.GetConverter(typeof(int)); // get the degrees, minutes and seconds value int Degrees = (int)IntConverter.ConvertFromString(context, culture, StringValue.Substring(0, DirectionPos)); int Minutes = (int)IntConverter.ConvertFromString(context, culture, StringValue.Substring(DirectionPos + 1, MinutesPos - DirectionPos - 1)); int Seconds = (int)IntConverter.ConvertFromString(context, culture, StringValue.Substring(MinutesPos + 1, SecondsPos - MinutesPos - 1)); // create a new Longitude instance with these values and return it return new Longitude(Degrees, Minutes, Seconds, Direction); } // otherwise call the base converter else return base.ConvertFrom(context, culture, value); }

有了这个转换类LongitudeTypeConverter,该怎么使用呢。其实很简单就是使用我们上面实现的四个方法,

class Test { public static void Main(string[] args) { //将Longitude类转换到string类型 Longitude longitude = new Longitude(10,11,12,LongitudeDirection.East); LongitudeTypeConverter converter = new LongitudeTypeConverter(); string strLongitude=""; if (converter.CanConvertTo(typeof(string))) { strLongitude = (string)converter.ConvertTo(longitude, typeof(string)); } System.Console.WriteLine(strLongitude); //将string还原回Longitude类 Longitude longitude1 = new Longitude(); if (converter.CanConvertFrom(typeof(string))) { longitude1 = (Longitude)converter.ConvertFrom(strLongitude); } System.Console.WriteLine(longitude1.Degrees); System.Console.WriteLine(longitude1.Direction); System.Console.WriteLine(longitude1.Minutes); System.Console.WriteLine(longitude1.Seconds); } }

输出结果是
10E11'12''
10
East
11
12
从结果中我们可以看到实现了我们预期的效果。
这些在一般代码里面可以用到,但从转换的结果中我们可以联想,web页面设计的两种模式(设计模式,源代码模式),在源代码模式我们显示的是string,但在设计模式我们显示控件的外观,这里就关系到TypeConverter类了,当然还有TypeConverterAtrribute。
下面就要说下这个TypeConverterAtrribute了。

TypeConverterAttribute Class
    TypeConverterAttribute 其实就是一个继承Attribute的类,使用[TypeConverter(typeof(MyClassConverter))]标签施加到程序实体上。根据TypeConverterAttritue的定义知道这个属性Attribute可以施加到所有实体上。

[AttributeUsageAttribute(AttributeTargets.All)] public sealed class TypeConverterAttribute : Attribute

如果你对Attribute不太了解可以先看看dudu的阐释,或者看看http://www.codeproject.com/KB/cs/attributes.aspx。
Attribute是一种新的申明方式,它可以在是设计时和运行时作用于它附加的Program Entities上。
上篇我们定义了class Longitude 和 class LongitudeTypeConverter,然后我们做了个Test,实现了转换的目的。
但要在设计时或在运行时起作用,就是说在这两种情况LongitudeTypeConverter“自动的”帮助Longitude 实例于其他实例转换,需要TypeConverterAttribute的帮忙。
在coding中我们很惊奇的发现,只用在Longitude类上附加TypeConverterAttribute标签,这两者就能关联起来。为什么呢?

[TypeConverter(typeof(LongitudeTypeConverter))] public class Longitude { //... }

那是因为如果一个类附件了Attribute,那么这个类就可以通过反射方法得到这个类属性,还可以通过TypeDescriptor.GetConverter静态方法获得这个类相关转换类的实例。这样就轻松的关联起来了。比如Test

class Test { public static void Main(string[] args) { //将Longitude类转换到string类型 Longitude longitude = new Longitude(10,11,12,LongitudeDirection.East); LongitudeTypeConverter converter = new LongitudeTypeConverter(); string strLongitude=""; if (converter.CanConvertTo(typeof(string))) { //Longitude 类没有附件TypeconverterAttribute时 strLongitude = (string)converter.ConvertTo(longitude, typeof(string)); //Longitude 类附件了TypeconverterAttribute时 strLongitude = (string)TypeDescriptor.GetConverter(typeof(Longitude)).ConvertTo(longitude, typeof(string)); } System.Console.WriteLine(strLongitude); //将string还原回Longitude类 Longitude longitude1 = new Longitude(); if (converter.CanConvertFrom(typeof(string))) { //Longitude 类没有附件TypeconverterAttribute时 longitude1 = (Longitude)converter.ConvertFrom(strLongitude); //Longitude 类附件了TypeconverterAttribute时 longitude1 = (Longitude)TypeDescriptor.GetConverter(typeof(Longitude)).ConvertFrom(strLongitude); } System.Console.WriteLine(longitude1.Degrees); System.Console.WriteLine(longitude1.Direction); System.Console.WriteLine(longitude1.Minutes); System.Console.WriteLine(longitude1.Seconds); } }

上面是在运行时,如果Longitude类的方法或字段也附加了相应的ConverterAttribute,我们也可以通过反射的方法得到附加ConverterAttribute的方法或字段。例如:

Type type = typeof(Longitude); //AttributeTargs包括method实体 CustomTypeConverterAttribute customTypeConverterAttribute; foreach (Attribute att in type.GetCustomAttributes(typeof(OtherTypeConverter), true)) { System.Console.WriteLine("OtherTypeConverter 的属性Property" + customTypeConverterAttribute.Property1); } foreach (MethodInfo method in type.GetMethods()) { foreach (Attribute att in method.GetCustomAttributes(true)) { customTypeConverterAttribute = att as CustomTypeConverterAttribute; if (null != customTypeConverterAttribute) { System.Console.WriteLine("类的方法是:" + method.Name + " 该方法的附加Attribute是:" + customTypeConverterAttribute.ToString()); } } }

如果你想test上面的代码,需要自己完成CustomTypeConverterAttribute。

在设计时的应用,例如在复杂控件中的一个属性为一个类,我们需要在property browser中以string的形式输入值来初始化或构造这个属性property,我们需要在这个属性property上附件属性DesignerSerializationVisibility标签。例如:

[Bindable(true)] [Category("Appearance")] [DefaultValue(typeof(GPSLocation), "")] [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] [PersistenceMode(PersistenceMode.InnerProperty)] public GPSLocation Location { get { // if no instance created yet do so if (_Location == null) _Location = new GPSLocation(); return _Location; } }

上面还有PersistenceMode属性标签,其表示代码在asp.net页面显示(也可以说是持久)的方式,有几种详见http://www.cnblogs.com/thinhunan/archive/2006/12/10/588341.html

这样就可以达到效果如下([PersistenceMode(PersistenceMode.Attribute)]

<ui:LocationControl ID="LocationControl1" runat="server" Location-GPSLatitude="12N1'2"" Location-GPSLongitude="24W3'4""> </ui:LocationControl>

[PersistenceMode(PersistenceMode.InnerProperty)]

<ui:LocationControl ID="LocationControl1" runat="server"> <Location GPSLatitude="12N1'3"" GPSLongitude="24W3'4"" /> </ui:LocationControl>

参考
http://www.codeproject.com/KB/webforms/TypeConverters.aspx
http://www.codeproject.com/KB/cs/attributes.aspx
dudu:Attribute系列 http://www.cnblogs.com/ericwen/favorite/115549.html

不对之处请批评指正。

【测试代码下载(请点击)】


(转于:http://blog.csdn.net/luyifeiniu/archive/2009/12/30/5107832.aspx)

 

 

你可能感兴趣的:(exception,object,String,null,Constructor,Descriptor)