類型轉換的作用,是實現PropertyGrid輸入的多個文本信息,能夠與對象進行有效的轉化,比如我們具有如下一個對象:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace AlbertControlExample.Controls { /// <summary> /// 定義一個新控件 /// </summary> public class AlbertControlDef : Control { public OffsetDef offsetDef { get; set; } = new OffsetDef(0,0); public AlbertControlDef() { } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); } } /// <summary> /// 定義一個左邊偏移 /// </summary> public class OffsetDef{ public OffsetDef(int left, int top) { this.Left = left; this.Top = top; } public double Left { get; set; } public double Top { get; set; } } }
我們看一下顯示當前的控件,會發現OffsetDef並不會顯示屬性,且無法編輯,如圖:

這是由於系統並無法解析OffsetDef對象,意思無法將它轉化為可以描述的文本集合,就不能對當前對象進行描述,那我們就需要利用TypeConverter對象,其可以定義如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace AlbertControlExample.Controls { /// <summary> /// 定義一個新控件 /// </summary> public class AlbertControlDef : Control { [TypeConverter(typeof(OffsetConverterDef))] public OffsetDef offsetDef { get; set; } = new OffsetDef(100,100); public AlbertControlDef() { } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); } } /// <summary> /// 定義一個左邊偏移 /// </summary> public class OffsetDef{ public OffsetDef(int left, int top) { this.Left = left; this.Top = top; } public double Left { get; set; } public double Top { get; set; } } public class OffsetConverterDef : TypeConverter { /// <summary> /// 是否支持屬性顯示 /// </summary> /// <param name="context"></param> /// <returns></returns> public override bool GetPropertiesSupported(ITypeDescriptorContext context) { return true; } /// <summary> /// 返回屬性文本的集合定義 /// </summary> /// <param name="context"></param> /// <param name="value"></param> /// <param name="attributes"></param> /// <returns></returns> public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) { var properties= TypeDescriptor.GetProperties(value); return properties; } } }
通過返回屬性集合,系統會默認顯示屬性到窗體,其顯示結果如下:

TypeDescriptor肯定不止這么簡單,其有幾個重要的函數,可以實現對象和輸入框之間的互相轉換,下面分別說明集合函數的功能和作用
/// <summary> /// 能否將對象轉換為字符串 /// </summary> /// <param name="context"></param> /// <param name="destinationType"></param> /// <returns></returns> public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { return true; } /// <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, CultureInfo culture, object value, Type destinationType) { if (value.GetType() == typeof(OffsetDef)) { OffsetDef offsetDef = value as OffsetDef; return string.Format("{0},{1}",offsetDef.Left, offsetDef.Top); } return base.ConvertTo(context, culture, value, destinationType); }
其顯示會如下:

當前定義是將對象轉化為字符串對象,並且顯示在當前對象對應的文本框之中。以上界面,我們通過修改100,100這個文本框是無法修改的,它只能轉換過來,假如我們想可以編輯他,並且自動轉換為對象,可以實現如下幾個函數:
/// <summary> /// 是否能從文本轉化為對象 /// </summary> /// <param name="context"></param> /// <param name="sourceType"></param> /// <returns></returns> public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return true; } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value.GetType() == typeof(string)) { string objDec = value.ToString(); string[] vsp = objDec.Split(','); if (vsp.Length == 2) { OffsetDef offsetDef = new OffsetDef(int.Parse(vsp[0]),int.Parse(vsp[1])); return offsetDef; } } return base.ConvertFrom(context, culture, value); }
通過以上的定義,我們發現,現在修改100,100這個文本框的內容,對象的屬性定義會自動改變,那是因為只要修改對象所對應的文本框,就會調用ConvertFrom函數,將當前文本框的內容自動轉化為對象,但是不能輸入錯誤的值,比如輸入一個無法轉換為int的字符串,那么就會報錯。其結果顯示如下:

但是其發現另外一個問題,我們修改100,100為100,200是否,那么對應的left和top的值會自動變化,但是我們修改Left/top的值的時候,並沒有影響到offsetDef的值,那是因為修改left\top不會觸發ConvertFrom函數,所以以上的轉化就會出問題,那我們怎么解決這個問題呢,則需要實現TypeDescriptor另外兩個函數:
/// <summary> /// 是否能重新創建對象,默認是不創建,當前是需要創建的,所以我們直接返回true /// </summary> /// <param name="context"></param> /// <returns></returns> public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) { return true; } /// <summary> /// 返回創建的實例對象,這個函數的調用,只要當前屬性列表發生變化,當前函數都會啟動調用,也會返回當前所有的屬性列表 /// </summary> /// <param name="context"></param> /// <param name="propertyValues"></param> /// <returns></returns> public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) { if (propertyValues.Count == 2) { OffsetDef def = new OffsetDef(int.Parse(propertyValues["Left"].ToString()), int.Parse(propertyValues["Top"].ToString())); return def; } return base.CreateInstance(context, propertyValues); }
通過以上兩個函數,你會發現,修改任何屬性對應的文本,那么這個對象就會重新定義,對象也會跟着改變。這個對象不僅僅有這些功能,其還有幾個非常重要的函數。
public override bool GetStandardValuesSupported(ITypeDescriptorContext context) { return true; } public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) { StandardValuesCollection standardValues = new StandardValuesCollection(new string[]{ "100,200","200,300","400,500"}); return standardValues; }
通過以上函數,可以對當前的對象指定一個標准的值,供用戶下拉選擇,通過以上的代碼,則可以實現如下功能:

同時還有其他幾個函數,這里就不一一說明,通過TypeConverter的定義,我們可以實現屬性文本列表和對象的互相轉換,實現對象的可配置。當前對象還有一個很重要的特性沒有說明,就是attributes和CultureInfo,ITypeDescriptorContext三個對象:
- attributes對象當然是對每個Property屬性上的attribute獲取和進行訪問的列表
- CultureInfo 主要用於實現控件的國際化的定義
- ITypeDescriptorContext 主要是當前類型標識的上下文信息,當前類型定義並沒有指定TypeDescriptor對象,所以當前對象為空,我們在接下來的章節,會介紹此對象。
