PropertyGrid—添加屬性Tab


零.引言

  PropertyGrid用來顯示和編輯對象的屬性,前面已經簡單介紹了如何使用該控件和提供不同的屬性編輯方法。前面主要講如何使用該控件,但有時,該控件無法滿足我們的需求,就需要對其進行擴展。本文主要介紹如何在PropertyGrid中添加屬性選項卡(PropertyTab)。VS自帶的屬性框有屬性和事件兩個屬性卡,下面簡單說明如何添加自己的選項卡。

一.PropertyGrid的組成

  在添加選項卡之前,先來看一看PropertyGrid的組成,分析其組成對后面設計十分有用。微軟將PropertyGrid封裝的十分好了,使用起來十分的方便,但是其具體如何實現的卻不得而知。翻看MSDN,找到一點皮毛。

  首先看一下PropertyGrid類,比較復雜,發現其有一個PropertyTabs屬性,應該是PropertyGrid所包含的選項卡集合,其類型是PropertyGrid.PropertyTabCollection,很顯然它是一個PropertyTab集合類。接着看一下PropertyTab類,下面是MSDN中的描述:

  • 為屬性選項卡提供基類
  • PropertyTab 類為屬性選項卡提供基類行為。屬性選項卡顯示在“屬性窗口”的 PropertyGrid 控件的工具欄上,並且允許組件顯示其屬性或其他數據的不同視圖。
  • 用戶代碼通常不會直接創建 PropertyTab 的實例。而可以將一個 PropertyTabAttribute(它指示為某一組件顯示的一個或多個屬性選項卡的類型)與應該顯示 PropertyTab 的屬性或類型相關聯。
  • PropertyGrid 將實例化一個與所瀏覽組件的類型或屬性字段關聯的 PropertyTabAttribute 所指定類型的 PropertyTab。

  很顯然,我們創建選項卡就是要以它為基類。看一下他的成員,還好,比較簡單,只有四個屬性,其中有一個Bitmap,是用來設置選項卡的圖標的,一個是TabName,很明顯是選項卡名稱,還有一個GetProperties方法,非常的重要,獲取指定組件的屬性信息集合,返回的是PropertyDescriptorCollection類型,很顯然他是一個PropertyDescriptor的集合。請記住這三個成員,后面會有用到的。

    既然返回了PropertyDescriptor,我們就來看一看它,PropertyDescriptor類是描述屬性信息的一個類,接着看,看PropertyDescriptor是如何描述屬性信息的,MSDN中這樣描述的:

      屬性的說明由名稱、其特性、與該屬性關聯的組件類和該屬性的類型組成。

      PropertyDescriptor 提供以下屬性和方法:

  •   Converter 包含此屬性的 TypeConverter。
  •   IsLocalizable 指示該屬性是否應該本地化。
  •   GetEditor 返回指定類型的編輯器。

      PropertyDescriptor 還提供以下 abstract 屬性和方法:

  •   ComponentType 包含該屬性綁定到的組件的類型。
  •   IsReadOnly 指示該屬性是否是只讀的。
  •   PropertyType 獲取屬性的類型。
  •   CanResetValue 指示重置組件是否會更改該組件的值。
  •   GetValue 返回組件上屬性的當前值。
  •   ResetValue 重置組件屬性的值。
  •   SetValue 將組件的值設置為一個不同的值。
  •   ShouldSerializeValue 指示是否需要持久保存該屬性的值。

      通常,abstract 成員是通過反射實現的。有關反射的更多信息,請參見 反射 中的主題。

  可見它非常詳細的描述了一個屬性的信息。而且從中好像看到了很多熟悉的東西,類型轉換,指定編輯器,設計時序列化,是的,在前面幾篇文章中介紹的一些功能,其實在內部就是通過這個類來實現的。

  再重新仔細看一下PropertyGrid類,發現有一個SelectedGridItem的屬性,返回的是GridItem類型,MSDN如是說:

    每個 GridItem 都對應於 SelectedObject 的一項屬性。

    可以使用返回的 GridItem 查看選定對象的類型信息、PropertyDescriptor、父對象和子對象。

  看一下這個GridItem。MSDN是這樣寫道的:

    實現 PropertyGrid 中的一行。

    網格項將視圖的層次結構表示為 PropertyGrid。可以使用 GridItem 獲取關於網格狀態和內容的信息。

    不應緩存 GridItem 對象,因為它們表示訪問它們時 PropertyGrid 的狀態的快照,而且網格活動可能釋放它們。PropertyGrid 經常內部重新創建 GridItem 對象,而不更改呈現給用戶的視圖。

  也就是說PropertyGrid的視圖是由一個一個GridItem壘起來的,而且只是狀態的快照,你在改變屬性排序,展開,折疊等的時候,PropertyGrid可能就會釋放,創建GridItem。來看一下GridItem的組成,他有一個GridItems屬性,說明GridItem是可以有子項的,也就是屬性中的子屬性;還有一個PropertyDescriptor屬性,就是上面PropertyTab中GetProperties返回來的類型,他用來描述與此GridItem相連的屬性的信息,以便將屬性顯示出來。

  下面這張圖簡單描述PropertyGrid的組成:

  

  從以上的分析,大致可以看出PropertyGrid是如何工作的,當把某一對象賦值給PropertyGrid的SelectedObject屬性時,PropertyGrid通知選中的PropertyTab,PropertyTab通過他的GetProperties方法,獲取並返回這個對象的一個屬性信息集合(PropertyDescriptorCollection),PropertyGrid獲取到這個集合后,分析其內容,為集合中的每個元素(每個屬性)創建一個GridItem,並將GridItem組織起來顯示出來,也就是我們所看到的屬性內容了。

  由此可見,我們對屬性的操作是通過GridItem進行的,而GridItem又通過PropertyDescriptor來對屬性進行操作,在PropertyDescriptor中會看到很多我們之前見到過的東西,如類型轉換,屬性編輯器,屬性設計時序列化等,其實這些都是通過PropertyDescriptor來實現的(詳情可見MSDN)。

三.添加屬性選項卡(PropertyTab)

  了解了PropertyGrid的組成后,再來添加選項卡就很清晰了。首先我們知道要新建一個選項卡類,並且要繼承於PropertyTab。重寫PropertyTab的TabName和Bitmap屬性,用來設置選項卡的外觀和名字;重寫GetProperties方法,來設置我們這個選項卡中要顯示哪些屬性,只需返回一個我們要顯示的屬性的PropertyDescriptorCollection,至於后面創建GridItem和顯示的工作就交給PropertyGrid自己去完成就行了。

  下面舉個例子:同樣的,假如我們有一個控件類:

  
 1     public class MyControl : System.Windows.Forms.UserControl
 2     {
 3         private double _angle = 90;
 4         
 5         public MyControl()
 6         {
 7         }
 8 
 9         [BrowsableAttribute(true)]
10         [Category("角度")]
11         public double Angle
12         {
13             get
14             { return _angle; }
15             set
16             { _angle = value; }
17         }
18 
19 
20         protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
21         {
22             e.Graphics.DrawString("The Angle is " + _angle, this.Font, Brushes.Red, 0, 0);
23         }
24     }
MyControl

  控件類有一個Angle的屬性,以及它從UerControl繼承過來的很多屬性,在VS的屬性框中會全部的顯示出來,如果現在我們只需要顯示在“角度”這一分類中的([Category("角度")])屬性。我們新建一個選項卡類,讓他只顯示“角度”這一分類中的屬性。

  
 1 //自定義選項卡類
 2  [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
 3     public class MyPropertyTab : PropertyTab
 4     {
 5         [BrowsableAttribute(true)]
 6         //位圖文件
 7         private string img = "AAEAAAD/////AQAAAAAAAAAMAgAAAFRTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0xLjAuMzMwMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA9gAAAAJCTfYAAAAAAAAANgAAACgAAAAIAAAACAAAAAEAGAAAAAAAAAAAAMQOAADEDgAAAAAAAAAAAAD///////////////////////////////////9ZgABZgADzPz/zPz/zPz9AgP//////////gAD/gAD/AAD/AAD/AACKyub///////+AAACAAAAAAP8AAP8AAP9AgP////////9ZgABZgABz13hz13hz13hAgP//////////gAD/gACA/wCA/wCA/wAA//////////+AAACAAAAAAP8AAP8AAP9AgP////////////////////////////////////8L";
 8 
 9         public MyPropertyTab()
10         {
11         }
12 
13         //返回我們要顯示的屬性的信息集合
14         public override System.ComponentModel.PropertyDescriptorCollection GetProperties(object component, System.Attribute[] attributes)
15         {
16             //獲取對象所有屬性
17 PropertyDescriptorCollection props;
18             if (attributes == null)
19                 props = TypeDescriptor.GetProperties(component);
20             else
21                 props = TypeDescriptor.GetProperties(component, attributes);
22 
23             //篩選“角度“屬性
24             int angleCount = 0;
25             for (int i = 0; i < props.Count; i++)
26             {
27                 if (props[i].Category == "角度")
28                 {
29                     angleCount++;
30                 }
31             }
32             PropertyDescriptor[] ps = new PropertyDescriptor[angleCount];
33             int j = 0;
34             for (int i = 0; i < props.Count; i++)
35             {
36                 PropertyDescriptor des = props[i];
37                 if (des.Category == "角度")
38                 {
39                     ps[j] = des;
40                     j++;
41                 }
42             }
43            
44             return new PropertyDescriptorCollection(ps);
45         }
46 
47         public override System.ComponentModel.PropertyDescriptorCollection GetProperties(object component)
48         {
49             return this.GetProperties(component, null);
50         }
51 
52         // Tab的名字.
53         public override string TabName
54         {
55             get
56             {
57                 return "AngleProperty";
58             }
59         }
60 
61         // Tab的圖標
62         public override System.Drawing.Bitmap Bitmap
63         {
64             get
65             {
66                 Bitmap bmp = new Bitmap(DeserializeFromBase64Text(img));
67                 return bmp;
68             }
69         }
70 
71         // 從字符串中獲取位圖
72         private Image DeserializeFromBase64Text(string text)
73         {
74             Image img = null;
75             byte[] memBytes = Convert.FromBase64String(text);
76             IFormatter formatter = new BinaryFormatter();
77             MemoryStream stream = new MemoryStream(memBytes);
78             img = (Image)formatter.Deserialize(stream);
79             stream.Close();
80             return img;
81         }
82 
83 
84     }
MyPropertyTab

  可見我們重寫了GetProperties方法,該方法從對象的屬性中篩選出“角度”類屬性;重寫了TabName,讓其為AngleProperty;重寫Bitmap,是選項卡的圖標,這里是從字符串中獲取的位圖,也可以從項目資源中獲取。

  設計好選項卡后,如何讓它出現在PropertyGrid中呢,有兩種方法:

  1. 如果這個選項卡只是針對我們一個特定的類,就如上面那個例子,選項卡顯示“角度”類型屬性,並不是所有的類都有“角度”屬性,選項卡只對有“角度”的類型有意義,那么,我們就讓它在選中了那樣的類型時才顯示,不然就不顯示,怎么做呢,很簡單,只需要給那樣的類(有“角度”屬性的類)添加一個特性:PropertyTab特性,例如在我們的MyControl類型上加上

  [PropertyTabAttribute(typeof(MyPropertyTab), PropertyTabScope.Component)]

  第一個參數中是我們自己定義的選項卡類,注意這里第二個參數使用的是 PropertyTabScope.Component,它表明只對這種類型的屬性使用MyPropertyTab選項卡。PropertyTabScope.Document表明只要在此文檔中,屬性選項卡就一直顯示(MSDN中說得,一直不太明白什么意思)。其他兩個不能使用。

  2. 如果這個選項卡是針對所有類型的,他需要一直顯示,那么我們就直接把他加到我們的PropertyGrid控件中。上面說了PropertyGrid有個PropertyTabs的屬性,將我們自定義的選項卡類添加到這個集合中即可,例如:

  this.propertyGrid1.PropertyTabs.AddTabType(typeof(MyPropertyTab));

  當然,這樣添加后,他針對所有的類型的對象都有效,就不需要再給特定的類添加PropertyTab特性了。

  本例中,我們使用第一種方法,添加PropertyTab特性。即在我們定義的MyControl類上加上[PropertyTabAttribute(typeof(MyPropertyTab), PropertyTabScope.Component)]。好了,下面讓我們來看一下效果:

  

  可見我們自定義的選項卡顯示在PropertyGrid中了,而且只顯示了“角度”類屬性。

四.完整代碼

  以下是完整的代碼:

  
  1 using System;
  2 using System.ComponentModel;
  3 using System.ComponentModel.Design;
  4 using System.Drawing;
  5 using System.IO;
  6 using System.Reflection;
  7 using System.Runtime.Serialization;
  8 using System.Runtime.Serialization.Formatters.Binary;
  9 using System.Windows.Forms;
 10 using System.Windows.Forms.Design;
 11 using System.Collections.Generic;
 12 
 13 namespace TestAddTab
 14 {
 15     //控件類
 16     [PropertyTabAttribute(typeof(MyPropertyTab), PropertyTabScope.Component)]
 17     public class MyControl : System.Windows.Forms.UserControl
 18     {
 19         private double _angle = 90;
 20         
 21         public MyControl()
 22         {
 23         }
 24 
 25         [BrowsableAttribute(true)]
 26         [Category("角度")]
 27         public double Angle
 28         {
 29             get
 30             { return _angle; }
 31             set
 32             { _angle = value; }
 33         }
 34 
 35 
 36         protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
 37         {
 38             e.Graphics.DrawString("The Angle is " + _angle, this.Font, Brushes.Red, 0, 0);
 39         }
 40     }
 41 
 42     //自定義選項卡類
 43  [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
 44     public class MyPropertyTab : PropertyTab
 45     {
 46         [BrowsableAttribute(true)]
 47         //位圖文件
 48         private string img = "AAEAAAD/////AQAAAAAAAAAMAgAAAFRTeXN0ZW0uRHJhd2luZywgVmVyc2lvbj0xLjAuMzMwMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWIwM2Y1ZjdmMTFkNTBhM2EFAQAAABVTeXN0ZW0uRHJhd2luZy5CaXRtYXABAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA9gAAAAJCTfYAAAAAAAAANgAAACgAAAAIAAAACAAAAAEAGAAAAAAAAAAAAMQOAADEDgAAAAAAAAAAAAD///////////////////////////////////9ZgABZgADzPz/zPz/zPz9AgP//////////gAD/gAD/AAD/AAD/AACKyub///////+AAACAAAAAAP8AAP8AAP9AgP////////9ZgABZgABz13hz13hz13hAgP//////////gAD/gACA/wCA/wCA/wAA//////////+AAACAAAAAAP8AAP8AAP9AgP////////////////////////////////////8L";
 49 
 50         public MyPropertyTab()
 51         {
 52         }
 53 
 54         //返回我們要顯示的屬性的信息集合
 55         public override System.ComponentModel.PropertyDescriptorCollection GetProperties(object component, System.Attribute[] attributes)
 56         {
 57             //獲取對象所有屬性
 58 PropertyDescriptorCollection props;
 59             if (attributes == null)
 60                 props = TypeDescriptor.GetProperties(component);
 61             else
 62                 props = TypeDescriptor.GetProperties(component, attributes);
 63 
 64             //篩選“角度“屬性
 65             int angleCount = 0;
 66             for (int i = 0; i < props.Count; i++)
 67             {
 68                 if (props[i].Category == "角度")
 69                 {
 70                     angleCount++;
 71                 }
 72             }
 73             PropertyDescriptor[] ps = new PropertyDescriptor[angleCount];
 74             int j = 0;
 75             for (int i = 0; i < props.Count; i++)
 76             {
 77                 PropertyDescriptor des = props[i];
 78                 if (des.Category == "角度")
 79                 {
 80                     ps[j] = des;
 81                     j++;
 82                 }
 83             }
 84            
 85             return new PropertyDescriptorCollection(ps);
 86         }
 87 
 88         public override System.ComponentModel.PropertyDescriptorCollection GetProperties(object component)
 89         {
 90             return this.GetProperties(component, null);
 91         }
 92 
 93         // Tab的名字.
 94         public override string TabName
 95         {
 96             get
 97             {
 98                 return "AngleProperty";
 99             }
100         }
101 
102         // Tab的圖標
103         public override System.Drawing.Bitmap Bitmap
104         {
105             get
106             {
107                 Bitmap bmp = new Bitmap(DeserializeFromBase64Text(img));
108                 return bmp;
109             }
110         }
111 
112         // 從字符串中獲取位圖
113         private Image DeserializeFromBase64Text(string text)
114         {
115             Image img = null;
116             byte[] memBytes = Convert.FromBase64String(text);
117             IFormatter formatter = new BinaryFormatter();
118             MemoryStream stream = new MemoryStream(memBytes);
119             img = (Image)formatter.Deserialize(stream);
120             stream.Close();
121             return img;
122         }
123     }
124 }
View Code

  新建Windows工程,添加該文件,將我們的MyControl控件拖入Form中,選中控件,看我們定義的tab是否出現,再選擇Form,看是否消失。

  根據本文方法,我們可以創建各種自定義屬性選項卡,顯示的內容關鍵在於你的GetProperties函數返回什么樣的值。

  當然,全文純屬個人愚見,錯誤荒謬之處,還望指出,不甚感激!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM