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 }

  新建Windows工程,添加该文件,将我们的MyControl控件拖入Form中,选中控件,看我们定义的tab是否出现,再选择Form,看是否消失。

  根据本文方法,我们可以创建各种自定义属性选项卡,显示的内容关键在于你的GetProperties函数返回什么样的值。

  当然,全文纯属个人愚见,错误荒谬之处,还望指出,不甚感激!

时间: 2024-08-24 06:24:20

PropertyGrid—添加属性Tab的相关文章

PropertyGrid—添加EventTab

零.引言 前面一篇文章介绍了如何在PropertyGrid中添加属性Tab,本文主要介绍如何添加事件选项卡.事件在许多对象中都有,尤其是在控件中,如何让对象的事件在PropertyGrid中显示出来呢,本文将进行简单的说明. 一.回顾添加属性Tab 在上篇文章中详细的讲解了如何添加属性Tab,这里简单回顾一下: 1.新建一个Tab类并继承于PropertyTab. 2.重写TabName和Bitmap属性以及GetProperties方法. 3.给特定类添加PropertyTab特性或将自定义T

PropertyGrid—默认属性,默认事件,属性默认值

零.引言 PropertyGrid显示一个对象的属性和事件时,可以设置其默认属性和事件,也就是当你选中对象时,propertyGrid中焦点在哪一个属性或事件上.为对象的属性提供默认值,使PropertyGrid显示属性时,更加友好. 一.默认属性和默认事件 PropertyGrid能识别默认属性和事件,例如在设计时,双击Form框,就会跳到Form的Load事件中,这是因为Form的默认事件是Load.当你选中属性框中的某一项后,该项会着色(蓝色)选中,在属性和事件选项卡之间切换,就会发现,选

跟陈湾来完善C++(2), 添加属性功能

上面几篇文章中,我们添加了名称空间优化,添加事件功能.这些对我来说其实已经够了.但还可以加一个属性功能. 当我们在C++中更改一个属性时,平常都是Get函数加上Set函数,但是这样,没有直接写一个成员变量方便.例如: a.SetValue(a.GetValue() + 1); 没有 a.Value = a.Value + 1; 方便. 但是这种方便只有在调用有属性功能的对象时才能使用.在创建属性的时候我还是用老套路,写一个Get和Set函数,该干啥还是干啥.我的属性功能其实就是在类中添加一个共有

runtime-给系统已有类添加属性

在没有接触runtime之前,我们接触到的能给类进行扩展的方法有类目(category)和延展(extension)两种.类目(category)可以给系统已有类添加扩展方法但是不能添加属性,并且被添加的方法可以被此类的子类所继承:延展(extension)为我们的自定义类添加属性和方法,但是添加的属性和方法都是私有的,在此类的子类中是无法访问的.那么问题来了,如果我们想给系统已有类添加一些方便我们使用的属性要怎么办呢?上述这两种方法中能给系统已有类添加的东西的就只有类目(category)了.

ios动态添加属性的几种方法

http://blog.csdn.net/shengyumojian/article/details/44919695 在ios运行过程中,有几种方式能够动态的添加属性. 1-通过runtime动态关联对象 主要用到了objc_setAssociatedObject,objc_getAssociatedObject以及objc_removeAssociatedObjects [objc] view plain copy //在目标target上添加关联对象,属性名propertyname(也能用

JavaScript自定义事件,动态添加属性

根据事件的不同,可用的自定义方法也不同. document.createEvent('Event'); 实现主要有4个步骤: 1.创建事件. 2.初始化事件(三个参数:事件名,是否起泡,是否取消默认触发) 3.监听事件 4.触发事件 var Evt = document.createEvent('Event');//创建一个事件 Evt.initEvent('inputChangeEvt', true, true);//初始化事件,给定事件名字 window.addEventListener('

prototype为对象添加属性和方法

可以通过prototype来为已经定义好的的"类"添加属性和方法.这里来了解一下prototype的基础知识.prototype是"构造函数"的属性,不是实例的属性. 示例: function HiClass() { this.sayHi = function(){ alert("hi"); } } var obj = new HiClass(); alert(HiClass.prototype);//outputs [object, objec

快速上手Runtime(二)之给分类添加属性

我们都知道,分类是不能直接添加属性的,那么我们有时候又需要实现这个功能,那么我们应该怎么办才能为分类添加上属性呢. Runtime给分类添加属性原理 给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间. Runtime给分类添加属性步骤 1.在分类的.h文件中声明想要定义的属性 2.在分类的.m文件中实现getter和setter方法 3.引入runtime头文件,然后在setter方法中用objc_setAssociatedObject关联对象 比如给N

【JavaScript】浅析JavaScript对象如何添加属性和方法

向JavaScript类中添加属性和方法,最直观的做法就是在类中定义属性和方法.JavaScript是一门弱语言,除了直接定义还可以用prototype来添加. 下面介绍从外部向JavaScript添加属性和方法的四种方法,首先定义一个类 function MyClass(){} 1,使用类名添加属性 MyClass.prototype.myname="吴兴国01"; 2,使用类名添加方法 MyClass.prototype.myfunc=function(){alert("