在Visual Basic中,属性窗口是真正实现快速应用开发的一个关键工具(RAD:Rapid Application Development)。在Visual Studio .NET中,属性窗口提供更多的特性来支持快速开发。
简介
在Visual Basic中,属性窗口是真正实现快速应用开发的一个关键工具(RAD:Rapid Application Development)。在Visual Studio .NET中,属性窗口提供更多的特性来支持快速开发。如果你在使用Visual Studio .NET开发环境编写组件或其他对象,可以用到属性窗口提供给的特性来丰富你的组件的设计时特性。
属性窗口可以做什么 以前的属性窗口版本处理基于COM的信息并且显示控件的内在属性。一个COM组件的公开API一般用IDL(Interface Definition Language)来声明,并且都有自己的属性。比如:nonbrowsable可以让属性窗口不显示它,或者是bindable可以让属性实现数据绑定。其他的显示特性,比如standard value list(标准值列表)和categorized properties(属性类别)需要组件实现COM接口IPerPropertyBrowsing和ICategorizedProperties。.NET framework和属性窗口以一种更加简单、统一的方式提供这些支持,同时有更多的新特性。
自然,.NET属性窗口继续支持以前版本的功能,他从ITypeInfo中得到类型信息并且支持上面提到过的特性。不过,如果要使用功能强大的新特性,就必须用managed code来实现组件。下面是列出一些新特性:
1. lMetadata attribute(元数据特性)
属性的特性很大程度上决定了属性窗口怎么和你的组件交互。特性可以很方便的让组件编写者来控制属性在属性窗口中是否可见、如何分类、是否可以包括在多选中、是否影响其他属性的值。这些特性都可以很方便地使用。
2. lHierarchical support(继承支持)
属性还可以拥有逻辑子属性。
3. lGraphical value representation(属性值图形化表示)
除了可以提供属性值的文字表示外,我们还可以提供属性值的图形化表示。
4. lCustom type editing(订制类型编辑)
组件可以提供自定义的用于属性编辑的用户界面,比如日期控件的日期属性的选择方式,或者是色彩控件的颜色选取方式。现在,不再由属性窗口来决定所支持的类型,而是组件来决定。Framework提供了很多工具来支持所有内嵌类型的编辑。
5. lExtensible views(扩展属性视图)
像“属性页(property tabs)”一样,组件可以在属性和事件上增加自己的视图,这样在设计状态就可以支持属性的图形化了。
6. lReusable component(重用组件)
.NET属性窗口主要是使用System.Windows.Forms.PropertyGrid控件组成的,我们同样可以在我们的应用程序运行时使用它的特性。
很明显,属性窗口还有更多的特性。这篇文章就是告诉你如何利用这些特性来扩展你自己的组件的特性。
基础知识:使用Attribute来订制属性窗口的显示 控制显示的机制和用IDL定义的组件是一样的,不过是增加了元数据特性。控制显示使用最普遍的特性是BrowsableAttribute。默认状态下,属性窗口显示对象中定义的所有的公开的、可读的(即public、有get或者set方法的)属性,并且把他们放在“杂项(Misc)”类别中。下面是一个简单的组件例子:
public class SimpleComponent : System.ComponentModel.Component
{
private string data = "(none)";
private bool dataValid = false;
public string Data
{
get
{
return data;
}
set
{
if (value != data)
{
dataValid = true;
data = value;
}
}
}
public bool IsDataValid
{
get
{
// perform some check on the data
//
return dataValid;
}
}
}
下图是这个例子在属性窗口中的显示:
图1.显示在属性窗口中的简单组件
在这个例子中,SimpleComponent有两个属性:Data和IsDataValid。实际上,由于IsDataValid是只读的,因此显示在这里并没有多大意义,设计人员在设计状态没有必要知道这个属性的值。因此,我们给他加上BrowsableAttribute特性让属性窗口不显示他。
[Browsable(false)]
public bool IsDataValid
{
get
{
// perform some check on the data
//
return dataValid;
}
}
编译器会自动在特性类名后添加“Attribute”字符,因此我们可以在代码中省略掉他。当然,输入“[BrowsableAttribute(false)]”是一样的效果。对于那些没有指定特性的属性或者类,编译器都使用默认特性和默认特性值加以描述。在这个例子中,BrowsableAttribute的默认值为true。这个原则对于Visual Basic .NET同样是一致的。两者唯一的区别就是Visual Basic .NET使用尖括号(‘<’和‘>’)来标记特性,而不是在C#中使用的中括号(‘[’和‘]’)。
编译器会自动在特性类名后添加“Attribute”字符,因此我们可以在代码中省略掉他。当然,输入“[BrowsableAttribute(false)]”是一样的效果。对于那些没有指定特性的属性或者类,编译器都使用默认特性和默认特性值加以描述。在这个例子中,BrowsableAttribute的默认值为true。这个原则对于Visual Basic .NET同样是一致的。两者唯一的区别就是Visual Basic .NET使用尖括号(‘<’和‘>’)来标记特性,而不是在C#中使用的中括号(‘[’和‘]’)。
同时,我们注意一下在图1中Data属性的值“abc”是粗体。这意味着属性的值不是默认值,而且这个值在设计器为form或者control生成代码的时候将会保存下来(即会生成一个赋值语句)。而对于属性的默认值就没有必要来生成赋值语句,生成代码意味着增加组件初始化的时间(InitializeComponent方法)和代码文件的大小。那么SimpleComponent该如何将默认值通知属性窗口的呢?要实现这个特性,我们就需要使用DefaultValueAttribute特性对属性加以描述,也就可以在对象的构建器中为属性赋值。当属性窗口显示属性值的时候,它就会比较当前值和DefaultValueAttribute指定的默认值,如果两者不相等的话,就会把值显示成粗体。在下面的例子里,Data属性的值如果不是“(none)”就会被显示成粗体。
[DefaultValue("(none)")]
public string Data
{
// . . .
}
我们同样可以给属性添加更为复杂的判断逻辑而不只是一些简单的固有值的比较,这可以通过给组件增加一些特殊方法加以实现。属性判断逻辑方法的名字必须以“ShouldSerialize”开头,并且接着就是属性的名字,而且此方法的返回值为“Boolean”。在这个例子里,这种方法就叫“ShouldSerializeData”。在SimpleComponent组件中增加下面的代码就可以实现和DefaultValueAttribute同样的效果,不过他却可以有更强的逻辑代码。
private bool ShouldSerializeData()
{
return Data != "(none)";
}
一般来说,将属性分类对设计者来说界面更加友好。我们就是用CategoryAttribute特性来给属性分类。这个特性就使用一个简单的类目字符串,属性窗口可以据此将属性显示在类目的子项中。类目名称可以自行决定。
[DefaultValue("(none)"), Category("Sample")]
public string Data
{
// . . .
}
组件开发者经常遇到的一个问题就是如何实现这个类目字符串的本地化。我们看看CategoryAttribute类,就可以看到他的GetLocalizedString方法就提供了这样的功能。要实现类目字符串的本地化,就要从CategoryAttribute类派生新的特性类。在这个例子里,我们从组件的字串资源中得到以键值为索引的本地化的类目字符串。在指定属性的CategoryAttribute特性时,用这个键值(Key)替换原来的类目名作为输入参数。这样属性窗在查询属性的CategoryAttribute就会调用GetLocalizedString方法并且把key值作为参数传入方法,在属性窗口中显示返回属性的类目名称。
internal class LocCategoryAttribute : CategoryAttribute
{
public LocCategoryAttribute(string categoryKey) : base(categoryKey)
{
}
protected override string GetLocalizedString(string key)
{
// get the resource set for the current locale.
//
ResourceManager resourceManager = new ResourceManager();
string categoryName = null;
// walk up the cultures until we find one with
// this key as a resource as our category name
// if we reach the invariant culture, quit.
//
for (CultureInfo culture = CultureInfo.CurrentCulture;