PropertyGrid 控件簡介[轉]
PropertyGrid 控件簡介
如果您使用過 Microsoft? Visual Basic? 或 Microsoft Visual Studio .NET,那么您一定使用過屬性瀏覽器來瀏覽、查看和編輯一個或多個對象的屬性。.NET 框架 PropertyGrid 控件是 Visual Studio .NET 屬性瀏覽器的核心。PropertyGrid 控件顯示對象或類型的屬性,并主要通過使用反射來檢索項目的屬性。(反射是在運行時提供類型信息的技術。)
下面的屏幕快照顯示了 PropertyGrid 在窗體上的外觀。
圖 1:窗體上的 PropertyGrid
PropertyGrid 包含以下部分:
- 屬性
- 可展開屬性
- 屬性類別標題
- 屬性說明
- 屬性編輯器
- 屬性選項卡
- 命令窗格(顯示控件設計器提供的設計器操作)
創建 PropertyGrid 控件
要使用 Visual Studio .NET 創建 PropertyGrid 控件,需要將該控件添加到工具箱中,因為默認情況下并不包含該控件。在 Tools (工具)菜單中,選擇 Customize Toolbox (自定義工具箱)。在對話框中選擇 Framework Components (框架組件)選項卡,然后選擇 PropertyGrid 。
如果您從命令行編譯代碼,請使用 /reference 選項并指定 System.Windows.Forms.dll。
以下代碼顯示了如何創建 PropertyGrid 控件并將其添加到窗體中。
- using System;
- using System.Drawing;
- using System.ComponentModel;
- using System.Windows.Forms;
- using System.Globalization;
- public class OptionsDialog : System.Windows.Forms.Form
- {
- private System.Windows.Forms.PropertyGrid OptionsPropertyGrid;
- public OptionsDialog()
- {
- OptionsPropertyGrid = new PropertyGrid();
- OptionsPropertyGrid.Size = new Size(300, 250);
- this.Controls.Add(OptionsPropertyGrid);
- this.Text = "選項對話框";
- }
- [STAThread]
- static void Main()
- {
- Application.Run(new OptionsDialog());
- }
- }
何處使用 PropertyGrid 控件 在應用程序中的很多地方,您都可以使用戶與 PropertyGrid 進行交互,從而獲得更豐富的編輯體驗。例如,某個應用程序包含多個用戶可以設置的“設置”或選項,其中一些可能十分復雜。您可以使用單選按鈕、組合框或文本框來表示這些選項。但本文將逐步介紹如何使用 PropertyGrid 控件創建選項窗口來設置應用程序選項。上面所創建的 OptionsDialog 窗體即是選項窗口的開始。現在,我們創建一個名為 AppSettings 的類,其中包含映射到應用程序設置的所有屬性。如果創建單獨的類而不使用多個分散的變量,設置將更便于管理和維護。
- public class AppSettings{
- private bool saveOnClose = true;
- private string greetingText = "歡迎使用應用程序!";
- private int itemsInMRU = 4;
- private int maxRepeatRate = 10;
- private bool settingsChanged = false;
- private string appVersion = "1.0";
- public bool SaveOnClose
- {
- get { return saveOnClose; }
- set { saveOnClose = value;}
- }
- public string GreetingText
- {
- get { return greetingText; }
- set { greetingText = value; }
- }
- public int MaxRepeatRate
- {
- get { return maxRepeatRate; }
- set { maxRepeatRate = value; }
- }
- public int ItemsInMRUList
- {
- get { return itemsInMRU; }
- set { itemsInMRU = value; }
- }
- public bool SettingsChanged
- {
- get { return settingsChanged; }
- set { settingsChanged = value; }
- }
- public string AppVersion
- {
- get { return appVersion; }
- set { appVersion = value; }
- }
- }
選項窗口上的 PropertyGrid 將使用此類,因此請將類定義添加到應用程序項目中,在添加時可創建新文件或將其添加到現有窗體源代碼的下方。
選擇對象
要標識 PropertyGrid 顯示的內容,請將 PropertyGrid.SelectedObject 屬性設置為一個對象實例。然后,PropertyGrid 將完成其余的工作。每次設置 SelectedObject 時,PropertyGrid 都會刷新顯示的屬性。這提供了一種簡單的方法來強制刷新屬性,或在運行時切換對象。您還可以調用 PropertyGrid.Refresh 方法來刷新屬性。
接下來,您需要更新 OptionsDialog 構造函數中的代碼,以創建一個 AppSettings 對象,并將其設置為 PropertyGrid.SelectedObject 屬性的值。
- public OptionsDialog()
- {
- OptionsPropertyGrid = new PropertyGrid();
- OptionsPropertyGrid.Size = new Size(300, 250);
- this.Controls.Add(OptionsPropertyGrid);
- this.Text = "選項對話框";
- // 創建 AppSettings 類并在 PropertyGrid 中顯示該類。
- AppSettings appset = new AppSettings();
- OptionsPropertyGrid.SelectedObject = appset;
- }
編譯并運行該應用程序。下面的屏幕快照顯示了應用程序的外觀。
圖 2:PropertyGrid 中選定的 AppSettings 類 自定義 PropertyGrid 控件
您可以修改 PropertyGrid 的某些外觀特征以滿足自己的需要??梢愿哪承傩缘娘@示方式,甚至選擇不顯示某些屬性。那么,如何對 PropertyGrid 進行自定義呢?
更改 PropertyGrid 的外觀特征
PropertyGrid 的許多外觀特征都可以自定義。下面列出了其中的一部分:
- 通過 HelpBackColor 、HelpForeColor 和 HelpVisible 屬性可以更改背景顏色、更改字體顏色或隱藏說明窗格。
- 通過 ToolbarVisible 屬性可以隱藏工具欄,通過 BackColor 屬性可以更改工具欄的顏色,通過 LargeButtons 屬性可以顯示大工具欄按鈕。
- 使用 PropertySort 屬性可以按字母順序對屬性進行排序和分類。
- 通過 BackColor 屬性可以更改拆分器的顏色。
- 通過 LineColor 屬性可以更改網格線和邊框。
本示例中的選項窗口不需要工具欄,因此可以將 ToolbarVisible 設置為 false 。其余屬性均保留默認設置。
更改屬性的顯示方式 要更改某些屬性的顯示方式,您可以對這些屬性應用不同的特性。特性是用于為類型、字段、方法和屬性等編程元素添加批注的聲明標記,在運行時可以使用反射對其進行檢索。下面列出了其中的一部分:
- DescriptionAttribute - 設置顯示在屬性下方說明幫助窗格中的屬性文本。這是一種為活動屬性(即具有焦點的屬性)提供幫助文本的有效方法??梢詫⒋颂匦詰糜?
MaxRepeatRate屬性。 - CategoryAttribute - 設置屬性在網格中所屬的類別。當您需要將屬性按類別名稱分組時,此特性非常有用。如果沒有為屬性指定類別,該屬性將被分配給雜項 類別??梢詫⒋颂匦詰糜谒袑傩浴?/li>
- BrowsableAttribute – 表示是否在網格中顯示屬性。此特性可用于在網格中隱藏屬性。默認情況下,公共屬性始終顯示在網格中??梢詫⒋颂匦詰糜?
SettingsChanged屬性。 - ReadOnlyAttribute – 表示屬性是否為只讀。此特性可用于禁止在網格中編輯屬性。默認情況下,帶有 get 和 set 訪問函數的公共屬性在網格中是可以編輯的??梢詫⒋颂匦詰糜?
AppVersion屬性。 - DefaultValueAttribute – 表示屬性的默認值。如果希望為屬性提供默認值,然后確定該屬性值是否與默認值相同,則可使用此特性??梢詫⒋颂匦詰糜谒袑傩?。
- DefaultPropertyAttribute – 表示類的默認屬性。在網格中選擇某個類時,將首先突出顯示該類的默認屬性??梢詫⒋颂匦詰糜?
AppSettings類。
現在,我們將其中的一些特性應用于 AppSettings 類,以更改屬性在 PropertyGrid 中的顯示方式。
- [DefaultPropertyAttribute("SaveOnClose")]
- public class AppSettings{
- private bool saveOnClose = true;
- private string greetingText = "歡迎使用應用程序!";
- private int maxRepeatRate = 10;
- private int itemsInMRU = 4;
- private bool settingsChanged = false;
- private string appVersion = "1.0";
- [CategoryAttribute("文檔設置"),
- DefaultValueAttribute(true)]
- public bool SaveOnClose
- {
- get { return saveOnClose; }
- set { saveOnClose = value;}
- }
- [CategoryAttribute("全局設置"),
- ReadOnlyAttribute(true),
- DefaultValueAttribute("歡迎使用應用程序!")]
- public string GreetingText
- {
- get { return greetingText; }
- set { greetingText = value; }
- }
- [CategoryAttribute("全局設置"),
- DefaultValueAttribute(4)]
- public int ItemsInMRUList
- {
- get { return itemsInMRU; }
- set { itemsInMRU = value; }
- }
- [DescriptionAttribute("以毫秒表示的文本重復率。"),
- CategoryAttribute("全局設置"),
- DefaultValueAttribute(10)]
- public int MaxRepeatRate
- {
- get { return maxRepeatRate; }
- set { maxRepeatRate = value; }
- }
- [BrowsableAttribute(false),
- DefaultValueAttribute(false)]
- public bool SettingsChanged
- {
- get { return settingsChanged; }
- set { settingsChanged = value; }
- }
- [CategoryAttribute("版本"),
- DefaultValueAttribute("1.0"),
- ReadOnlyAttribute(true)]
- public string AppVersion
- {
- get { return appVersion; }
- set { appVersion = value; }
- }
- }
將這些特性應用于 AppSettings 類后,編譯并運行該應用程序。下面的屏幕快照顯示了應用程序的外觀。
圖 3:PropertyGrid 中顯示的帶有類別和默認值的屬性
使用此版本的選項窗口后,您會注意到以下幾點:
- 顯示窗口時,將首先突出顯示
SaveOnClose屬性。 - 選中
MaxRepeatRate屬性時,說明幫助窗格中將顯示“以毫秒表示的文本重復率”。 SaveOnClose屬性顯示在“文檔設置”類別下。其他屬性分別顯示在“全局設置”和“版本”類別下。SettingsChanged屬性將不再顯示。AppVersion屬性為只讀。只讀屬性以灰顯文本顯示。- 如果
SaveOnClose屬性包含的值不是 true ,該值將以粗體顯示。PropertyGrid 使用粗體文本表示包含非默認值的屬性。
顯示復雜屬性
到現在為止,選項窗口顯示的都是簡單的類型,如整數、布爾值和字符串。那么,如何顯示更復雜的類型呢?如果應用程序需要跟蹤窗口大小、文檔字體或工具欄顏色等信息,該如何處理呢?.NET 框架提供的某些數據類型具有特殊的顯示功能,能使這些類型在 PropertyGrid 中更具可用性。
對所提供類型的支持 首先,請更新 AppSettings 類,為窗口大小(Size 類型)、窗口字體(Font 類型)和工具欄顏色(Color 類型)添加新屬性。
- [DefaultPropertyAttribute("SaveOnClose")]
- public class AppSettings{
- private bool saveOnClose = true;
- private string greetingText = "歡迎使用應用程序!";
- private int maxRepeatRate = 10;
- private int itemsInMRU = 4;
- private bool settingsChanged = false;
- private string appVersion = "1.0";
- private Size windowSize = new Size(100,100);
- private Font windowFont = new Font("宋體", 9, FontStyle.Regular);
- private Color toolbarColor = SystemColors.Control;
- [CategoryAttribute("文檔設置"),
- DefaultValueAttribute(true)]
- public bool SaveOnClose
- {
- get { return saveOnClose; }
- set { saveOnClose = value;}
- }
- [CategoryAttribute("文檔設置")]
- public Size WindowSize
- {
- get { return windowSize; }
- set { windowSize = value;}
- }
- [CategoryAttribute("文檔設置")]
- public Font WindowFont
- {
- get {return windowFont; }
- set { windowFont = value;}
- }
- [CategoryAttribute("全局設置")]
- public Color ToolbarColor
- {
- get { return toolbarColor; }
- set { toolbarColor = value; }
- }
- [CategoryAttribute("全局設置"),
- ReadOnlyAttribute(true),
- DefaultValueAttribute("歡迎使用應用程序!")]
- public string GreetingText
- {
- get { return greetingText; }
- set { greetingText = value; }
- }
- [CategoryAttribute("全局設置"),
- DefaultValueAttribute(4)]
- public int ItemsInMRUList
- {
- get { return itemsInMRU; }
- set { itemsInMRU = value; }
- }
- [DescriptionAttribute("以毫秒表示的文本重復率。"),
- CategoryAttribute("全局設置"),
- DefaultValueAttribute(10)]
- public int MaxRepeatRate
- {
- get { return maxRepeatRate; }
- set { maxRepeatRate = value; }
- }
- [BrowsableAttribute(false),
- DefaultValueAttribute(false)]
- public bool SettingsChanged
- {
- get { return settingsChanged; }
- set { settingsChanged = value; }
- }
- [CategoryAttribute("版本"),
- DefaultValueAttribute("1.0"),
- ReadOnlyAttribute(true)]
- public string AppVersion
- {
- get { return appVersion; }
- set { appVersion = value; }
- }
- }
下面的屏幕快照顯示了新屬性在 PropertyGrid 中的外觀。
圖 4:顯示在 PropertyGrid 中的 .NET 框架數據類型
請注意,WindowFont 屬性帶有一個省略號 (...) 按鈕,按下該按鈕將顯示字體選擇對話框。此外,還可以展開該屬性以顯示更多的 Font 屬性。某些 Font 屬性提供有關字體的值和詳細信息的下拉列表。您可以展開 WindowSize 屬性以顯示 Size 類型的更多屬性。最后,請注意,ToolbarColor 屬性包含一個選定顏色的樣本,以及一個用于選擇不同顏色的自定義下拉列表。對于這些以及其他數據類型,.NET 框架提供了其他的類,可以使在 PropertyGrid 中的編輯更加容易。
對自定義類型的支持
現在,您需要在 AppSettings 類中添加另外兩個屬性,即 DefaultFileName 和 SpellCheckOptions 。 DefaultFileName 屬性用于獲取或設置字符串;SpellCheckOptions 屬性用于獲取或設置 SpellingOptions 類的實例。
SpellingOptions 類是一個新類,用于管理應用程序的拼寫檢查屬性。對于何時創建單獨的類以管理對象的屬性,并沒有嚴格的規定,而取決于您的整個類設計。將 SpellingOptions 類定義添加到應用程序項目中 - 可以添加到新文件中,也可以添加到窗體源代碼的下方。
- [DescriptionAttribute("展開以查看應用程序的拼寫選項。")]
- public class SpellingOptions{
- private bool spellCheckWhileTyping = true;
- private bool spellCheckCAPS = false;
- private bool suggestCorrections = true;
- [DefaultValueAttribute(true)]
- public bool SpellCheckWhileTyping
- {
- get { return spellCheckWhileTyping; }
- set { spellCheckWhileTyping = value; }
- }
- [DefaultValueAttribute(false)]
- public bool SpellCheckCAPS
- {
- get { return spellCheckCAPS; }
- set { spellCheckCAPS = value; }
- }
- [DefaultValueAttribute(true)]
- public bool SuggestCorrections
- {
- get { return suggestCorrections; }
- set { suggestCorrections = value; }
- }
- }
再次編譯并運行選項窗口應用程序。下面的屏幕快照顯示了應用程序的外觀。
圖 5:在 PropertyGrid 中顯示的不帶類型轉換器的自定義數據類型
請注意 SpellcheckOptions 屬性的外觀。與 .NET 框架類型不同,它不展開或顯示自定義的字符串表示。如果要在自己的復雜類型中提供與 .NET 框架類型相同的編輯體驗,該如何處理呢?.NET 框架類型使用 TypeConverter 和 UITypeEditor 類提供大部分 PropertyGrid 編輯支持,您也可以使用這些類。
添加可展開屬性支持
要使 PropertyGrid 能夠展開 SpellingOptions 屬性,您需要創建 TypeConverter 。TypeConverter 提供了從一種類型轉換為另一種類型的方法。PropertyGrid 使用 TypeConverter 將對象類型轉換為 String ,并使用該 String 在網格中顯示對象值。在編輯過程中,TypeConverter 會將 String 轉換回對象類型。.NET 框架提供的 ExpandableObjectConverter 類可以簡化這一過程。
提供可展開對象支持
- 創建一個從 ExpandableObjectConverter繼承而來的類。
- public class SpellingOptionsConverter:ExpandableObjectConverter
- { }
- 如果
destinationType參數與使用此類型轉換器的類(示例中的SpellingOptions類)的類型相同,則覆蓋 CanConvertTo 方法并返回 true ;否則返回基類 CanConvertTo方法的值。- public override bool CanConvertTo(ITypeDescriptorContext context,
- System.Type destinationType)
- {
- if (destinationType == typeof(SpellingOptions))
- return true;
- return base.CanConvertTo(context, destinationType);
- }
- 覆蓋 ConvertTo 方法,并確保
destinationType參數是一個 String ,并且值的類型與使用此類型轉換器的類(示例中的SpellingOptions類)相同。如果其中任一情況為 false ,都將返回基類 ConvertTo 方法的值;否則,返回值對象的字符串表示。字符串表示需要使用唯一分隔符將類的每個屬性隔開。由于整個字符串都將顯示在 PropertyGrid中,因此需要選擇一個不會影響可讀性的分隔符,逗號的效果通常比較好。- public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture,
- object value, System.Type destinationType)
- {
- if (destinationType == typeof(System.String) &&
- value is SpellingOptions){
- SpellingOptions so = (SpellingOptions)value;
- return "在鍵入時檢查:" + so.SpellCheckWhileTyping +
- ",檢查大小寫: " + so.SpellCheckCAPS +
- ",建議更正: " + so.SuggestCorrections;
- }
- return base.ConvertTo(context, culture, value, destinationType);
- }
- (可選)通過指定類型轉換器可以從字符串進行轉換,您可以啟用網格中對象字符串表示的編輯。要執行此操作,首先需要覆蓋 CanConvertFrom 方法并返回 true (如果源 Type 參數為 String 類型);否則,返回基類 CanConvertFrom方法的值。
- public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
- {
- if (sourceType == typeof(string))
- return true;
- return base.CanConvertFrom(context, sourceType);
- }
- 要啟用對象基類的編輯,同樣需要覆蓋 ConvertFrom 方法并確保值參數是一個 String 。如果不是 String ,將返回基類 ConvertFrom 方法的值;否則,返回基于值參數的類(示例中的
SpellingOptions類)的新實例。您需要根據值參數解析類的每個屬性的值。了解在 ConvertTo方法中創建的分隔字符串的格式將有助于您的解析。- public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
- {
- if (value is string) {
- try {
- string s = (string) value;
- int colon = s.IndexOf(':');
- int comma = s.IndexOf(',');
- if (colon != -1 && comma != -1) {
- string checkWhileTyping = s.Substring(colon + 1 , (comma - colon - 1));
- colon = s.IndexOf(':', comma + 1);
- comma = s.IndexOf(',', comma + 1);
- string checkCaps = s.Substring(colon + 1 , (comma - colon -1));
- colon = s.IndexOf(':', comma + 1);
- string suggCorr = s.Substring(colon + 1);
- SpellingOptions so = new SpellingOptions();
- so.SpellCheckWhileTyping =Boolean.Parse(checkWhileTyping);
- so.SpellCheckCAPS = Boolean.Parse(checkCaps);
- so.SuggestCorrections = Boolean.Parse(suggCorr);
- return so;
- }
- }
- catch {
- throw new ArgumentException(
- "無法將“" + (string)value +
- "”轉換為 SpellingOptions 類型");
- }
- }
- return base.ConvertFrom(context, culture, value);
- }
- 現在已經有了一個類型轉換器類,下面您需要確定使用該類的目標類。您可以通過將 TypeConverterAttribute 應用到目標類(示例中的
SpellingOptions類)來執行此操作。- // 應用于 SpellingOptions 類的 TypeConverter 特性。
- [TypeConverterAttribute(typeof(SpellingOptionsConverter)),
- DescriptionAttribute(“展開以查看應用程序的拼寫選項。")]
- public class SpellingOptions{ ... }
再次編譯并運行選項窗口應用程序。下面的屏幕快照顯示了選項窗口目前的外觀。
圖 6:在 PropertyGrid 中顯示的帶有類型轉換器的自定義數據類型
注意: 如果只需要可展開對象支持,而不需要自定義字符串表示,則只需將 TypeConverterAttribute 應用到類中。將 ExpandableObjectConverter 指定為類型轉換器類型。
添加域列表和簡單的下拉列表屬性支持
對于基于 Enum 類型返回枚舉的屬性,PropertyGrid 會自動在下拉列表中顯示枚舉值。EnumConverter 也提供了這一功能。對于自己的屬性,您可能希望為用戶提供一個有效值列表(有時也稱為選取列表或域列表),而其類型并不是基于 Enum 。如果域值在運行時之前未知,或者值可以更改,則屬于這種情況。
修改選項窗口,提供一個用戶可從中選擇的默認文件名的域列表。您已經將 DefaultFileName 屬性添加到 AppSettings 類。下一步是在 PropertyGrid 中顯示屬性的下拉列表,以提供域列表。
提供簡單的下拉列表屬性支持
- 創建一個從類型轉換器類繼承而來的類。由于 DefaultFileName 屬性屬于 String 類型,因此可以從 StringConverter 中繼承。如果屬性類型的類型轉換器不存在,則可以從 TypeConverter繼承;這里并不需要。
- public class FileNameConverter: StringConverter { }
- 覆蓋 GetStandardValuesSupported 方法并返回 true,表示此對象支持可以從列表中選取的一組標準值。
- public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
- {
- return true;
- }
- 覆蓋 GetStandardValues 方法并返回填充了標準值的 StandardValuesCollection 。創建 StandardValuesCollection 的方法之一是在構造函數中提供一個值數組。對于選項窗口應用程序,您可以使用填充了建議的默認文件名的 String 數組。
- public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
- {
- return new StandardValuesCollection(new string[]{"新文件", "文件1", "文檔1"});
- }
- (可選)如果希望用戶能夠鍵入下拉列表中沒有包含的值,請覆蓋 GetStandardValuesExclusive 方法并返回 false。這從根本上將下拉列表樣式變成了組合框樣式。
- public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
- {
- return false;
- }
- 擁有自己的用于顯示下拉列表的類型轉換器類后,您需要確定使用該類的目標。在本示例中,目標為 DefaultFileName 屬性,因為類型轉換器是針對該屬性的。將 TypeConverterAttribute 應用到目標屬性中。
- // 應用到 DefaultFileName 屬性的 TypeConverter 特性。
- [TypeConverter(typeof(FileNameConverter)),
- CategoryAttribute("文檔設置")]
- public string DefaultFileName
- {
- get{ return defaultFileName; }
- set{ defaultFileName = value; }
- }
再次編譯并運行選項窗口應用程序。下面的屏幕快照顯示了選項窗口目前的外觀。請注意 DefaultFileName 屬性的外觀。
圖 7:在 PropertyGrid 中顯示下拉域列表
為屬性提供自定義 UI
如上所述,.NET 框架類型使用 TypeConverter 和 UITypeEditor 類(以及其他類)來提供 PropertyGrid 編輯支持。有關如何使用 TypeConverter ,請參閱對自定義類型的支持 一節;您也可以使用 UITypeEditor 類來自定義 PropertyGrid 。
您可以在 PropertyGrid 中提供小圖形表示和屬性值,類似于為 Image 和 Color 類提供的內容。要在自定義中執行此操作,請從 UITypeEditor 繼承,覆蓋 GetPaintValueSupported 并返回 true 。然后,覆蓋 UITypeEditor.PaintValue 方法,并在自己的方法中使用 PaintValueEventArgs. Graphics 參數繪制圖形。最后,將 Editor 特性應用到使用 UITypeEditor 類的類或屬性。
下面的屏幕快照顯示了結果外觀。 圖 8:在 PropertyGrid 中顯示屬性的自定義圖形
您也可以提供自己的下拉列表控件,這與 Control.Dock 屬性用來為用戶提供靠接選擇的控件類似。要執行此操作,請從 UITypeEditor 繼承,覆蓋 GetEditStyle ,然后返回一個 UITypeEditorEditStyle 枚舉值,例如 DropDown 。您的自定義下拉列表控件必須從 Control 或 Control 的派生類(例如 UserControl )繼承而來。然后,覆蓋 UITypeEditor.EditValue 方法。使用 IServiceProvider 參數調用 IServiceProvider.GetService 方法,以獲取一個 IWindowsFormsEditorService 實例。最后,調用 IWindowsFormsEditorService.DropDownControl 方法來顯示您的自定義下拉列表控件。請記住將 Editor 特性應用到使用 UITypeEditor 類的類或屬性中。
下面的屏幕快照顯示了結果外觀。 圖 9:在 PropertyGrid 中顯示屬性的自定義下拉列表控件
除了使用 TypeEditor 和 UITypeEditor 類外,還可以自定義 PropertyGrid 以顯示其他屬性選項卡。屬性選項卡從 PropertyTab 類繼承而來。如果您使用過 Microsoft Visual C#? .NET 中的屬性瀏覽器,那么就可能看到過自定義的 PropertyTab 。Events 選項卡(帶有閃電圖形的按鈕)就是一個自定義的 PropertyTab 。下面的屏幕快照顯示了自定義 PropertyTab 的另一個示例??梢允褂?PropertyTab 編輯按鈕的邊界點,以創建自定義的按鈕形狀。 圖 10:在 PropertyGrid 中顯示自定義選項卡
如果想在item中增加自定義的顯示方式,比如日期選擇啦、下拉框啦、甚至文件選擇、拾色器等等,我們可以參考如下:
改變 PropertyGrid 控件的編輯風格(1)加入日期控件
編輯日期類型數據
- using System;
- using System.Windows.Forms;
- using System.Drawing.Design;
- using System.Windows.Forms.Design;
- namespace blog.csdn.net.zhangyuk
- {
- /// <summary>
- /// 在PropertyGrid 上顯示日期控件
- /// </summary>
- public class PropertyGridDateItem : UITypeEditor
- {
- MonthCalendar dateControl = new MonthCalendar();
- public PropertyGridDateItem()
- {
- dateControl.MaxSelectionCount = 1;
- }
- public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
- {
- return UITypeEditorEditStyle.DropDown;
- }
- public override object EditValue(System.ComponentModel.ITypeDescriptorContext context,
- System.IServiceProvider provider, object value)
- {
- try
- {
- IWindowsFormsEditorService edSvc =
- (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
- if (edSvc != null)
- {
- if (value is string)
- {
- dateControl.SelectionStart = DateTime.Parse(value as String);
- edSvc.DropDownControl(dateControl);
- return dateControl.SelectionStart.ToShortDateString();
- }
- else if (value is DateTime)
- {
- dateControl.SelectionStart = (DateTime)value;
- edSvc.DropDownControl(dateControl);
- return dateControl.SelectionStart;
- }
- }
- }
- catch (Exception ex)
- {
- System.Console.WriteLine("PropertyGridDateItem Error : " + ex.Message);
- return value;
- }
- return value;
- }
- }
- }
步驟二:編輯屬性類,指定編輯屬性。示例如下:
- namespace blog.csdn.net.zhangyuk
- {
- public class SomeProperties
- {
- private string _finished_time = "";
- //……
- [Description("完成時間"), Category("屬性"), EditorAttribute(typeof(PropertyGridDateItem),
- typeof(System.Drawing.Design.UITypeEditor))]
- public String 完成時間
- {
- get { return _finished_date; }
- set { _finished_date = value; }
- }
- //……
- }
- }
步驟三:設置 PropertyGrid 的屬性對象。示例如下:
- private void Form1_Load(object sender, System.EventArgs e)
- {
- this.propertyGrid1.SelectedObject = new SomeProperties();
- }
改變 PropertyGrid 控件的編輯風格(2)——編輯多行文本
效果:
適用場合:
1、 編輯多行文本;
2、 編輯長文本。
步驟一:定義從UITypeEditor 派生的類,示例如下:
- using System;
- using System.Windows.Forms;
- using System.Drawing.Design;
- using System.Windows.Forms.Design;
- namespace blog.csdn.net.zhangyuk
- {
- /// <summary>
- /// PropertyGridMutiText 的摘要說明。
- /// </summary>
- public class PropertyGridRichText : UITypeEditor
- {
- public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
- {
- return UITypeEditorEditStyle.DropDown;
- }
- public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, System.IServiceProvider provider, object value)
- {
- try
- {
- IWindowsFormsEditorService edSvc =
- (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
- if (edSvc != null)
- {
- if (value is string)
- {
- RichTextBox box = new RichTextBox();
- box.Text = value as string;
- edSvc.DropDownControl(box);
- return box.Text;
- }
- }
- }
- catch (Exception ex)
- {
- System.Console.WriteLine("PropertyGridRichText Error : " + ex.Message);
- return value;
- }
- return value;
- }
- }
- }
步驟二:編輯屬性類,指定編輯屬性。示例如下:
- namespace blog.csdn.net.zhangyuk
- {
- public class SomeProperties
- {
- private string _finished_time = "";
- // ……
- // 多行文本編輯框
- string _mutiLineSample = "";
- [Description("多行文本編輯框"), Category("屬性"), EditorAttribute(typeof(PropertyGridRichText),
- typeof(System.Drawing.Design.UITypeEditor))]
- public String 多行文本
- {
- get { return _mutiLineSample; }
- set { _mutiLineSample = value; }
- }
- //……
- }
- }
步驟三:設置PropertyGrid的屬性對象。示例如下:
- private void Form1_Load(object sender, System.EventArgs e)
- {
- this.propertyGrid1.SelectedObject = new SomeProperties();
- }
改變 PropertyGrid 控件的編輯風格(3)——打開對話框
適用場合:
1、 打開文件、打印設置等通用對話框
2、 打開特定的對話框
步驟一:定義從UITypeEditor 派生的類,以 OpenFileDialog 對話框為例,示例代碼如下:
- using System;
- using System.Windows.Forms;
- using System.Drawing.Design;
- using System.Windows.Forms.Design;
- namespace blog.csdn.net.zhangyuk
- {
- /// <summary>
- /// IMSOpenFileInPropertyGrid 的摘要說明。
- /// </summary>
- public class PropertyGridFileItem : UITypeEditor
- {
- public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
- {
- return UITypeEditorEditStyle.Modal;
- }
- public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, System.
- IServiceProvider provider, object value)
- {
- IWindowsFormsEditorService edSvc =
- (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
- if (edSvc != null)
- {
- // 可以打開任何特定的對話框
- OpenFileDialog dialog = new OpenFileDialog();
- dialog.AddExtension = false;
- if (dialog.ShowDialog().Equals(DialogResult.OK))
- {
- return dialog.FileName;
- }
- }
- return value;
- }
- }
- }
步驟二:編輯屬性類,指定編輯屬性。示例如下:
- namespace blog.csdn.net.zhangyuk
- {
- public class SomeProperties
- {
- private string _finished_time = "";
- //……
- // 文件
- string _fileName = "";
- [Description("文件打開對話框"), Category("屬性"), EditorAttribute(typeof(PropertyGridFileItem),
- typeof(System.Drawing.Design.UITypeEditor))]
- public String 文件
- {
- get { return _fileName; }
- set { _fileName = value; }
- }
- //……
- }
- }
步驟三:設置PropertyGrid的屬性對象。示例如下:
- private void Form1_Load(object sender, System.EventArgs e)
- {
- this.propertyGrid1.SelectedObject = new SomeProperties();
- }
改變 PropertyGrid 控件的編輯風格(4)——加入選擇列表
適用場合:限制選擇輸入
步驟一:定義從UITypeEditor 繼承的抽象類:ComboBoxItemTypeConvert。示例如下:
- using System;
- using System.Collections;
- using System.ComponentModel;
- namespace blog.csdn.net.zhangyuk
- {
- /// IMSTypeConvert 的摘要說明。
- public abstract class ComboBoxItemTypeConvert : TypeConverter
- {
- public Hashtable _hash = null;
- public ComboBoxItemTypeConvert()
- {
- _hash = new Hashtable();
- GetConvertHash();
- }
- public abstract void GetConvertHash();
- public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
- {
- return true;
- }
- public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
- {
- int[] ids = new int[_hash.Values.Count];
- int i = 0;
- foreach (DictionaryEntry myDE in _hash)
- {
- ids[i++] = (int)(myDE.Key);
- }
- return new StandardValuesCollection(ids);
- }
- public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
- {
- if (sourceType == typeof(string))
- {
- return true;
- }
- return base.CanConvertFrom(context, sourceType);
- }
- public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object v)
- {
- if (v is string)
- {
- foreach (DictionaryEntry myDE in _hash)
- {
- if (myDE.Value.Equals((v.ToString())))
- return myDE.Key;
- }
- }
- return base.ConvertFrom(context, culture, v);
- }
- public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture,
- object v, Type destinationType)
- {
- if (destinationType == typeof(string))
- {
- foreach (DictionaryEntry myDE in _hash)
- {
- if (myDE.Key.Equals(v))
- return myDE.Value.ToString();
- }
- return "";
- }
- return base.ConvertTo(context, culture, v, destinationType);
- }
- public override bool GetStandardValuesExclusive(
- ITypeDescriptorContext context)
- {
- return false;
- }
- }
- }
步驟二:定義 ComboBoxItemTypeConvert 的派生類,派生類中實現父類的抽象方法:public abstract void GetConvertHash(); 示例如下:
- using System;
- using System.Collections;
- using System.ComponentModel;
- namespace blog.csdn.net.zhangyuk
- {
- public class PropertyGridBoolItem : ComboBoxItemTypeConvert
- {
- public override void GetConvertHash()
- {
- _hash.Add(0, "是");
- _hash.Add(1, "否");
- }
- }
- public class PropertyGridComboBoxItem : ComboBoxItemTypeConvert
- {
- public override void GetConvertHash()
- {
- _hash.Add(0, "炒肝");
- _hash.Add(1, "豆汁");
- _hash.Add(2, "灌腸");
- }
- }
- }
步驟三:編輯屬性類,指定編輯屬性。示例如下:
- namespace blog.csdn.net.zhangyuk
- {
- public class SomeProperties
- {
- private string _finished_time = "";
- // ……
- // 布爾
- bool _bool = true;
- [Description("布爾"), Category("屬性"), TypeConverter(typeof(PropertyGridBoolItem))]
- public int 布爾
- {
- get { return _bool == true ? 0 : 1; }
- set { _bool = (value == 0 ? true : false); }
- }
- // 選擇列表
- int _comboBoxItems = 0;
- [Description("選擇列表"), Category("屬性"), TypeConverter(typeof(PropertyGridComboBoxItem))]
- public int 選擇列表
- {
- get { return _comboBoxItems; }
- set { _comboBoxItems = value; }
- }
- //……
- }
- }
步驟四:設置PropertyGrid的屬性對象。示例如下:
- private void Form1_Load(object sender, System.EventArgs e)
- {
- this.propertyGrid1.SelectedObject = new SomeProperties();
- }
如果想改變item的displayname為中文我們該怎么辦呢?
運行時自定義PropertyGrid顯示屬性項目
簡述
在PropertyGrid所顯示的屬性內容包括屬性分類(Category)及組件屬性,
在一般情況下直接使用PropertyGrid來顯示一個對象的所有屬性是非常方便的,只需一個語句就能完成:
propertyGrid.SelectedObject = component;
但在實際應用中可能會不需要顯示所有屬性項目,而是通過外部指定(通過XML等進行描述),這些設置一般情況下在創建組件時用代碼中的Attribute來進行具體設置,如所屬分類,顯示標題等,這只能針對于一些自建的組件可以這么做。
問題描述
像上面所說,在創建自建組件時可以用Attribute的方式來設置PropertyGrid的顯示樣式,但這種方法不能應用于已有的組件,像系統中的TextBox,Button等,除非自己建立一個由這些組件派生的類,當然這樣做會加大復雜度。像要實現下面所顯示的這種效果在實際操作時會很麻煩。
左圖是TextBox原有的所有屬性,右圖是經過處理后的屬性
解決方法
在.Net中提供了一個自定義類型說明的接口(System.ComponentModel.ICustomTypeDescriptor),PropertyGrid可以直接自動處理用此接口生成的對象,因此在處理這個問題的時候只需要創建一個基于這個接口的處理類就可以達到世期望的目標,在這個接口中提供了GetProperties方法用于返回所選組件的所有屬性,因此我們可以通過這個方法可以對我們所需要的屬性進行過濾,下面是一段GetPropertys的處理代碼:
- public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
- {
- List<CustomPropertyDescriptor> tmpPDCLst = new List<CustomPropertyDescriptor>();
- PropertyDescriptorCollection tmpPDC = TypeDescriptor.GetProperties(mCurrentSelectObject, attributes);
- IEnumerator tmpIe = tmpPDC.GetEnumerator();
- CustomPropertyDescriptor tmpCPD;
- PropertyDescriptor tmpPD;
- while (tmpIe.MoveNext())
- {
- tmpPD = tmpIe.Current as PropertyDescriptor;
- if (mObjectAttribs.ContainsKey(tmpPD.Name))
- {
- tmpCPD = new CustomPropertyDescriptor(mCurrentSelectObject, tmpPD);
- tmpCPD.SetDisplayName(mObjectAttribs[tmpPD.Name]);
- //此處用于處理屬性分類的名稱,可以在XML等設置文件中進行設置,在這段代碼中只是簡單的在分類后加了“中文”兩個字
- tmpCPD.SetCategory(tmpPD.Category + "中文");
- tmpPDCLst.Add(tmpCPD);
- }
- }
- return new PropertyDescriptorCollection(tmpPDCLst.ToArray());
- }
當然在進行屬性過慮之后,PropertyGrid中所顯示的屬性名稱都還是原有名稱,若想同時改變在PropertyGrid中顯示出來的名稱則需要重寫PropertyDescriptor中的部分方法,在上面這段代碼中的CustomPropertyDescriptor就是一個基于PropertyDescriptor的類。
在CustomPropertyDescriptor類中最主要的是重寫DisplayName與Category這兩個屬性,但由于在PropertyDescriptor中這兩個屬性是只讀的,因此在這個類中需要加入兩個用于設置這兩個屬性的方法(或直接用Field)在這里我使用了SetDispalyName與SetCategory這兩個方法:
- private string mCategory;
- public override string Category
- {
- get { return mCategory; }
- }
- private string mDisplayName ;
- public override string DisplayName
- {
- get { return mDisplayName; }
- }
- public void SetDisplayName(string pDispalyName)
- {
- mDisplayName = pDispalyName;
- }
- public void SetCategory(string pCategory)
- {
- mCategory = pCategory;
- }
就這樣的幾步,便可以將PropertyGrid中顯示的內容完全自定義。
在寫ICustomTypeDescriptor接口時,其他的一些方法可以用TypeDescriptor直接返回相關方法調用,并在GetPropertyOwner方法中應返回當前選擇對象否則將不會對修改值起任何作用
- public object GetPropertyOwner(PropertyDescriptor pd)
- {
- return mCurrentSelectObject;
- }
在寫CustomPropertyDescriptor類時需要一個PropertyDescriptor對象,在實現一些方法時直接返回這個對象的值。
當然也可以通過這個方法來自定義一些Events的輸出,
使用方法
- //加載組件屬性,從XML文件載入,此處為Button
- XmlNode tmpXNode = mXDoc.SelectSingleNode("Components/Component[@Name=/"Button/"]");
- //選擇屬性設置
- XmlNodeList tmpXPropLst = tmpXNode.SelectNodes("Propertys/Property");
- //創建CustomProperty對象
- CustomProperty cp = new CustomProperty(sender, tmpXPropLst);
- //設置PropertyGrid選擇對象
- propertyGrid1.SelectedObject = cp;
對于顯示的item順序如果想自定義怎么辦呢?
- //
- // 摘要:
- // 使用該集合的默認排序(通常為字母順序)對集合中的成員進行排序。
- public virtual PropertyDescriptorCollection Sort();
- //
- // 摘要:
- // 使用指定的 System.Collections.IComparer 對此集合中的成員排序。
- public virtual PropertyDescriptorCollection Sort(IComparer comparer);
- //
- // 摘要:
- // 對此集合中的成員排序。首先應用指定的順序,然后應用此集合的默認排序,后者通常為字母順序。
- public virtual PropertyDescriptorCollection Sort(string[] names);
- //
- // 摘要:
- // 對此集合中的成員排序。首先應用指定的順序,然后使用指定的 System.Collections.IComparer 進行排序。
- public virtual PropertyDescriptorCollection Sort(string[] names, IComparer comparer);
- /// <summary>
- /// 返回此類型說明符所表示的對象的已篩選屬性描述符的集合。
- /// </summary>
- /// <param name="attributes">用作篩選器的屬性數組。它可以是 null。</param>
- /// <returns>
- /// 一個 <see cref="T:System.ComponentModel.PropertyDescriptorCollection"></see>,包含此類型說明符所表示的對象的屬性說明。默認為 <see cref="F:System.ComponentModel.PropertyDescriptorCollection.Empty"></see>。
- /// </returns>
- public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
- {
- int i = 0;
- //parameterList是外面傳入的待顯示數據。
- PropertyDescriptor[] newProps = new PropertyDescriptor[parameterList.Count];
- foreach (ConfigParameter parameter in parameterList)
- {
- //由ConfigParameter,你自己定義的類型,轉成PropertyDescriptor類型:
- newProps[i++] = new SystemOptionsPropertyDescriptor(parameter, true, attributes);
- }
- //取得了PropertyDescriptor[] newProps;現在可以對它排序,否則就按照newProps的原有順序顯示;
- //1.直接返回
- PropertyDescriptorCollection tmpPDC = new PropertyDescriptorCollection(newProps);
- return tmpPDC;
- //2.默認字母順序
- PropertyDescriptorCollection tmpPDC = new PropertyDescriptorCollection(newProps);
- return tmpPDC.Sort();
- //3.數組的順序:sortName就是屬性的順序,內容就是各個屬性名。
- string[] sortName = new string[] { "a","b","c","d" };
- return tmpPDC.Sort(sortName);
- //4.comparer規則定義的排序方式
- ParameterListComparer myComp = new ParameterListComparer();//ParameterListComparer : IComparer
- return tmpPDC.Sort(myComp);
- //5.先數組,后comparer
- return tmpPDC.Sort(sortName,myComp);
- //而我采用的是把數據傳入之前就排序完畢的方法:即調用之前,把數據parameterList排好順序,再 1.直接返回。
- }
- //對于數組排序方法,可以聲明稱一個事件,由外部傳入屬性的實現順序:
- public delegate string[] SortEventHandler();
- public class CustomTypeDescriptorExtend : CustomTypeDescriptor
- {
- public SortEventHandler OnSort;
- //......其它代碼
- public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
- {
- string[] sortName = OnSort();
- PropertyDescriptorCollection tmpPDC = TypeDescriptor.GetProperties(typeof(CustomTypeDescriptorExtend), attributes);
- return tmpPDC.Sort(sortName);
- }
- }
- //使用時:
- public MyMethod()
- {
- CustomTypeDescriptorExtend s = new CustomTypeDescriptorExtend();
- s.OnSort += new SortEventHandler(SetSortors);
- PropertyGrid1.SelectedObject = s;
- PropertyGrid1.PropertySort = PropertySort.Categorized;
- }
- public string[] SetSortors()
- {
- return new string[] { "B", "A", "C" };
- }
如果希望在程序運行中修改某些屬性值的話,應該怎么辦呢?
- void SetPropertyVisibility(object obj, string propertyName, bool visible)
- {
- Type type = typeof(BrowsableAttribute);
- PropertyDescriptorCollection props = TypeDescriptor.GetProperties(obj);
- AttributeCollection attrs = props[propertyName].Attributes;
- FieldInfo fld = type.GetField("browsable", BindingFlags.Instance | BindingFlags.NonPublic);
- fld.SetValue(attrs[type], visible);
- }
- void SetPropertyReadOnly(object obj, string propertyName, bool readOnly)
- {
- Type type = typeof(System.ComponentModel.ReadOnlyAttribute);
- PropertyDescriptorCollection props = TypeDescriptor.GetProperties(obj);
- AttributeCollection attrs = props[propertyName].Attributes;
- FieldInfo fld = type.GetField("isReadOnly", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.CreateInstance);
- fld.SetValue(attrs[type], readOnly);
- }
總之,PropertyGrid通過很多技巧能夠達到很炫的效果,不過由于PropertyGrid與對象嚴格綁定,需要大量的convert/uiedittype/attributes等配合實現,同時要注意在使用過程中PropertyGrid的改變就代表了內存對象的改變,這一點一定要記住。同時字段的屬性,特別是類型屬性一定要嚴格控制好,否則convert過程中就會異常了。

浙公網安備 33010602011771號