注:本文参考MSDN使用 .NET Framework 开发自定义 Windows 窗体控件一节,根据该文的结构对VS2005下开发自定义控件作了一个综合性的说明。本文将偏重于在开发自定义控件的过程中常见的难点的指导性介绍,而对MSDN中该节中已经说明的内容不会过多的重复,但会在必要的地方给出MSDN的链接以便你获得更多信息。在阅读MSDN时,初学者可以按照本文给出的链接阅读相关的内容,对在本文没有给出但在MSDN中出现的链接内容可以留待以后深入了解。
一、 为什么需要自定义控件
在.NET Framework的概念中,控件是指具有用户界面并实现了特定功能的一类可复用的单元,.NET的所有控件都是直接或者间接从Control类继承下来。
虽然VS2005已经为开发人员提供了众多简单实用的Windows窗体控件(以下统称控件),但是在许多开发过程中,开发人员可能会遇到以下情况:
1. 应用程序中很多用户界面具有相同或者相似的外观和功能,并且这类界面区域中包括多个相互关联的控件。比如常见的登陆界面都包含代表“用户名”、“用户密码”、“登陆”以及“取消登陆”等的多个控件,且它们共同完成了用户登陆的业务功能。
2. 现有的Windows控件功能基本能满足开发的需要,但你可能希望修改其部分功能或者添加一个新的功能并在以后的开发中得到复用。比如一个只接收数字的文本框控件,可以响应条码扫描的文本框控件等。
3. 开发中一些常用的界面外观或者功能希望得到复用,但却无法通过现有的Windows控件实现此目的。比如你可能在开发中需要一个具有时钟外观和功能的时钟控件。
二、 自定义控件的不同种类
针对前一节出现的不同情况,Windows 窗体支持三种用户定义的控件:复合控件、扩展控件和自定义控件。更多内容请参考MSDN自定义控件的各种不同类型
1. 复合控件
请阅读MSDN自定义控件的各种不同类型中关于复合控件的内容。
复合控件是在.NET应用程序开发中常用且重要的一种复用手段,它的开发也相对简单。开发复合可以从 UserControl 类派生。VS2005的“添加”菜单有“用户控件”一项,它实际上就是为用户添加一个从UserControl类派生的自定义类。
从开发复合控件的实际过程来说,开发复合控件与开发一个普通的Window窗体(Form控件)没有本质上的区别,UserControl类和Form类都是从System.Windows.Forms.ContainerControl继承,它们都是通过组合已有的控件及其功能来实现特定的功能。有关更多信息,请参见开发复合 Windows 窗体控件。
2. 扩展控件
请阅读MSDN自定义控件的各种不同类型中关于扩展控件的内容。
扩展控件也是在.NET应用程序开发中常用且重要的一种自定义控件开发手段。VS2005开发环境没有给出扩展控件的“添加项”模板,开发者可以先新建一个普通的类,然后将类改为从希望扩展其功能的现有控件类继承,此时这个新创建的类就已经是一个自定义的扩展控件了。然后开发者可以改写(override)基类的属性或者方法,也可以添加自定义的属性和方法来达到扩展控件功能的目的。比如,在WinCETools项目中的BarcodeTextBox就是从TextBox继承并对其GotFocus等方法进行了改写,同时也添加了IsDefault属性、DataArrived事件等新增的功能。
3. 自定义控件
此处的“自定义控件”是指从Control类继承从头开始创建一个控件的过程。在本节以外的地方,如不作特别说明,“自定义控件”还是指包括复合控件、扩展控件在内的控件的统称。
请阅读MSDN自定义控件的各种不同类型中关于自定义控件的内容。
完全自定义开发控件是一个比较复杂的过程,开发者不仅需要完全实现控件的业务逻辑,还需要通过重写OnPaint方法自行绘制控件的外观。有关更多信息,请参见如何:开发简单的 Windows 窗体控件。
三、 自定义控件开发示例
你可以参照MSDN 演练:使用 Visual C# 从 Windows 窗体控件继承来创建一个扩展的Button控件并体会开发一个简单的自定义控件的基本过程。
四、 控件的设计时支持
控件的设计时(design-time)支持是开发自定义控件的一个重要内容,也是开发的难点。那么什么是控件的设计时支持呢?
控件的设计时是指控件运行在开发环境的可视化设计器中的这一段时间,在这段时间内开发者使用它们在VS2005开发环境中设计用户界面以及设定控件的属性。与之相对的是控件的运行时,运行时是指应用程序编译生成后,控件随应用程序启动并运行的这一阶段。控件最终的是随应用程序发布并运行在程序的目标环境中的,为什么会有设计时这一概念呢?一般来说,开发者可能仅仅注意到控件必然有运行时的概念,因此也仅仅实现了控件在运行时的外观和功能逻辑。然而,控件还有一个重要的功能,那就是开发者需要使用它们来进行用户界面的可视化设计。在这一过程中,控件事实上也是一个运行在开发环境中的对象,这个对象由开发环境构造并销毁,开发者通过开发环境的可视化设计器来改变控件的属性。显然,控件对象在运行时的外观和逻辑是不可能和在开发环境中进行可视化设计时是完全一样的,一个最明显的例子就是,开发者在PC机器上的VS2005开发环境下开发WinCE应用程序,但该应用程序最终是部署在WinCE设备上运行的,因此用于开发WinCE设备应用程序的控件的设计时和运行时必然是有区别的。
了解了控件的设计时,那么控件的设计时支持即是指控件在处于设计时的时候应该表现出来的形态和功能逻辑,以及向开发环境的可视化设计器提供有价值的信息的功能。从上述定义可以看出,设计时支持包含两个方面的内容:一是可视化设计器需要的控件中的属性(Attributes),一是控件在运行时和设计时的不同形态和功能逻辑。下面一一介绍它们。
1. 控件中的属性(Attributes)
先来看个例子。下图所示是WinCETools中BarcodeTextBox控件的属性面板。
如图中所示的控件属性(Property)的分类和描述信息等对控件的运行时没有任何价值,但却给开发者使用控件带来了很大的帮助。如何让自己的控件提供这些信息呢?.NET提供的C#属性(Attributes)就是解决这个问题的办法。
在前面如何:开发简单的 Windows 窗体控件的例子中你可能注意到了下面一段代码:
[
Category("Alignment"),
Description("Specifies the alignment of text.")
]
publicContentAlignment TextAlignment
{
get
{
return alignmentValue;
}
set
{
alignmentValue =value;
// The Invalidate method invokes the OnPaint method described
// in step 3.
Invalidate();
}
}
这段代码中Category("Alignment")正是说明控件的TextAlignment属性属于Alignment类别,而Description("Specifies the alignment of text.")说明TextAlignment属性的描述信息为Specifies the alignment of text.。
.NET为控件的设计时提供了很多属性(Attributes),MSDNWindows 窗体控件中的属性列出了大部分常用的属性,其中下列属性(Attributes)及其用法应该最先掌握。
Ø BrowsableAttribute
Ø CategoryAttribute
Ø DefaultValueAttribute
Ø DescriptionAttribute
Ø DefaultEventAttribute
Ø DefaultPropertyAttribute
Ø ToolboxBitmapAttribute
在了解上述属性(Attributes)的过程中你可能会发现,这些属性都是.NET Framework完整版支持的类,而在.NET Compact Framework中不支持。那么如何为.NET Compact Framework的控件提供设计时属性呢?VS2005为开发者提供了另外一种解决办法,使用“设计时属性文件”。如下图:
设计时属性文件是一个XML格式的文件,它为设备程序集提供设计时信息。先来看看下面的设计时属性文件。
<?xmlversion="1.0"encoding="utf-16"?>
<Classesxmlns="http://schemas.microsoft.com/VisualStudio/2004/03/SmartDevices/XMTA.xsd">
<ClassName="WinCETools.Scan.BarcodeTextBox">
<DesktopCompatible>true</DesktopCompatible>
<DefaultEvent>DataArrived</DefaultEvent>
<PropertyName="IsDefault">
<Category>WinCETools</Category>
<DefaultValue>
<Type>System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</Type>
<Value>False</Value>
</DefaultValue>
<Description>获取和设置控件是否作为窗体中的默认扫描接收控件</Description>
<Browsable>false</Browsable>
</Property>
<EventName="DataArrived">
<Category>WinCETools</Category>
<Description>当条码扫描完成时触发的事件。</Description>
</Event>
</Class>
</Classes>
设计时属性文件中Classes节点是XML文件的根节点,其中每个Class节点都用于描述一个控件的设计时属性。比如上面的文件就是对WinCETools.Scan.BarcodeTextBox这个控件的设计时属性描述。仔细对比文件中的节点名称和之前列出的设计时属性(Attributes)类,我们很容易发现它们之间的对应关系。事实上,它们本身就是实现控件设计时属性的不同方式而已。
在编译WinCE智能设备项目的时候,VS2005首先生成项目的运行时程序集,当VS2005发现项目为该程序集提供了设计时属性文件时,VS2005为程序集中的每一个类在设计时文件中查找对应的设计时属性,然后重新为它们编译生成一个带有设计时属性的可在开发环境中运行的设计时程序集。例如为WinCETools.Client.dll生成文件名为WinCETools.Client.PocketPC.asmmeta.dll或者WinCETools.Client.WindowsCE.asmmeta.dll的程序集,文件名中WinCETools.Client是原程序集的名称,asmmeta是设计时程序集固有的后缀,而PocketPC和WindowsCE则分别对应项目的目标平台。你应该为你的控件准备各种智能设备目标平台的设计时程序集。
2. 控件的设计时逻辑
一般来说,控件在设计时改变属性值等过程中需要实现的逻辑仅仅是通知可视化设计器更新与该控件相关的代码需要改动,而并不需要完全按照控件运行时应该处理的改变属性的逻辑在开发环境中执行这些逻辑。有些更为特殊的逻辑甚至在开发环境中根本无法执行,比如WinCETools中的BarcodeTextBox控件的IsDefault属性,设置IsDefault属性为true时控件在运行时的逻辑应该是打开扫描头,这个逻辑显然在设计时执行会遇到困难。
鉴于上面提到的问题,控件的运行逻辑也可能分为设计时和运行时,我们在开发控件的时候也应该加以区别。那么如何让控件在设计时执行设计时的逻辑,而又不影响控件在运行时的功能呢?常用的办法是在实现控件时判断控件当前的运行环境,然后使控件执行相应的逻辑。有两种常见方法可以区别控件当前的运行形态:
Ø Control.Site属性的DesignMode
控件都有一个Site属性,Site属性是一个ISite接口的对象,该接口的DesignMode表明控件当前的运行形态,为true则表示控件处于设计时,否则处于运行时。
if (this.Site !=null&&this.Site.DesignMode)
{
// 设计时逻辑
}
else
{
// 运行时逻辑
}
Control.Site属性不被.NET Compact Framework 1.0支持,因此通过Control.Site的DesignMode来判断控件的设计时只能在开发非CF1.0的控件时可以采用。
Ø 判断System.Environment.OSVersion.Platform
在开发WinCE设备控件的时候,通过判断控件当前运行环境的操作系统平台来判断控件的设计时是一个简单且有效的办法。如下代码:
if (System.Environment.OSVersion.Platform ==PlatformID.WinCE)
{
// 运行时逻辑
}
else
{
// 设计时逻辑
}
五、 关于自定义控件的更多内容
1. 控件的工具箱图标
你可能会发现,VS2005自带的控件工具箱上有一个友好的图标,如何让自己的控件在添加到工具箱后也具有一个特别的图标呢?通过下面的步骤就可以实现这一点:
1) 制作一个bmp图片文件,并把它命名为和控件类名一样的名称;
2) 将图片添加到项目中并把它放在和控件一样的目录下,使其所处的命名空间和控件一致;
3) 选中图片并切换到属性视图,把“生成操作”设置为“嵌入的资源”;
4) 编译生成控件。
2. 开发自定义组件
在.NET中,组件是从Compenent继承下列并实现特定功能的类,控件也是组件的一种。通常把具有用户界面的组件称为控件,而不具有用户界面的称为组件。在VS2005下开发自定义组件和开发控件没有大的区别,VS2005开发环境也在“添加项”中包含了一般组件的模板。在此我们不特别介绍组件的开发。