
·您现在的位置: 云翼网络 >> 文章中心 >> 网站建设 >> 网站建设开发 >> ASP.NET网站开发 >> PropertyGrid—添加属性Tab
PRopertyGrid用来显示和编辑对象的属性,前面已经简单介绍了如何使用该控件和提供不同的属性编辑方法。前面主要讲如何使用该控件,但有时,该控件无法满足我们的需求,就需要对其进行扩展。本文主要介绍如何在PropertyGrid中添加属性选项卡(PropertyTab)。VS自带的属性框有属性和事件两个属性卡,下面简单说明如何添加自己的选项卡。
在添加选项卡之前,先来看一看PropertyGrid的组成,分析其组成对后面设计十分有用。微软将PropertyGrid封装的十分好了,使用起来十分的方便,但是其具体如何实现的却不得而知。翻看MSDN,找到一点皮毛。
首先看一下PropertyGrid类,比较复杂,发现其有一个PropertyTabs属性,应该是PropertyGrid所包含的选项卡集合,其类型是PropertyGrid.PropertyTabCollection,很显然它是一个PropertyTab集合类。接着看一下PropertyTab类,下面是MSDN中的描述:
很显然,我们创建选项卡就是要以它为基类。看一下他的成员,还好,比较简单,只有四个属性,其中有一个Bitmap,是用来设置选项卡的图标的,一个是TabName,很明显是选项卡名称,还有一个GetProperties方法,非常的重要,获取指定组件的属性信息集合,返回的是PropertyDescriptorCollection类型,很显然他是一个PropertyDescriptor的集合。请记住这三个成员,后面会有用到的。
既然返回了PropertyDescriptor,我们就来看一看它,PropertyDescriptor类是描述属性信息的一个类,接着看,看PropertyDescriptor是如何描述属性信息的,MSDN中这样描述的:
属性的说明由名称、其特性、与该属性关联的组件类和该属性的类型组成。
PropertyDescriptor 提供以下属性和方法:
PropertyDescriptor 还提供以下 abstract 属性和方法:
通常,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)。
了解了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函数返回什么样的值。
当然,全文纯属个人愚见,错误荒谬之处,还望指出,不甚感激!