用可缩进的.NET WinForms复合控件(composite controls)来扩展UI的灵活性。
by Douglas Gennetten and Jonathan Lurie
技术工具箱:C#
在设计UI时,我们总是面临的一个很大的挑战就是在添加功能的同时不要破坏可用性。在Web上实现这一点的一个很流行的做法就是运用可缩进的面板(collapsible panel)——它包含控件,用户可以随意隐藏和显示它们。在缩进面板时,每个面板只呈现一个小的标题栏和一个用来展开它的小的按钮。你可以用类似的、用处更多的元素改进你的WinForms应用程序中的UI。
我们将讲述如何创建一个强大的WinForms复合控件,它可以实现一组可缩进的面板。样例代码提供了一个功能很全的控件,它可以让你创建灵活的UI。该控件不只是个你在Web上可以看到的典型的可缩进的面板,它还可以自动管理一组面板的行为。例如,当你将控件设置到最节省空间的(space-conserving)模式时,打开任何面板都会自动缩进以前打开的任何面板,从而总是占用最小的界面空间。在另一种模式下,只有在有必要保持你指定的面板组的最多数量时,面板才会自动缩进。
在开始创建复合控件时,先创建一个Windows Control Library。该项目中包含两个不同的用户控件:CollapsingControl,用来缩进和弹出一个面板;和GroupingControl,它可以容纳一个或多个CollapsingControl。我们先创建CollapsingControl(见列表1)。你必须用类的Designer特性来完成创建。
在C#中,我们用方括号来标记特性。在类声明前运用Designer特性: Designer("System.Windows.Forms.Design.
ParentControlDesigner,System.
Design", typeof(System.
ComponentModel.Design.IDesigner))]
没有这个特性,你就不能添加任何控件到CollapsingControl。(另一种可选的方法就是将Panel类作为一个子类。然而,我们并没有选用那种方法,因此我们可以向你展示如何从头开始创建。)
CollapsingControl包含一个面板,它的Dock属性设置为Top。该面板包含一个label和一个linkLabel。你可以用label来显示对CollapsingControl的说明,将linkLabel用作一个按钮,用户通过点击它来缩进(<<)和弹出(>>)控件。将label的dock属性设置为Fill,将linkLabel的dock属性设置为Right。
可缩进的控件有两个高度:当前高度和弹出高度。你可以用一个私有字段和公有属性来存储、设置这些值。Browsable特性可以让你从VS.NET IDE的Properties窗口设置该属性,虽然用这种方法有些多余,因为缺省情况下,Browsable特性设置为true。然而,如果你不想从Properties窗口得到这些属性,你可以用下面的代码:[System.ComponentModel.Browsable
(false)]
将缩进控件的高度设置为label的高度。这个最小的高度可以是一个公开的属性,我们可以在以后增加其高度。当前的实现方式需要你手动地设置CollapsingControl的HeightExpanded属性。你可能想改变这种情况,使该属性可以自动设置,但这么做会不让你将控件的最初高度设置为最小高度。对Opened属性的设置可以决定在设计和运行时应该将一个CollapsingControl设置为Collapsed还是Expanded(缺省设置)。
设置标题
下一步是设置CollapsingControl的标题,就是label显示的文本。实现该步骤需要一个迂回的方法。你不能在C#中覆盖一个用户控件的Text属性,这么做是不合适的,因为你可能用Text属性来设置控件中label的Text属性。如果你试图用Text属性上的新的操作符来迂回实现该步骤(这会隐藏基类的Text属性),你就会碰到一个有趣的问题:从Properties窗口设置Text属性不能生成代码来设置这个值。这似乎是IDE中,而不是语言中的一个bug或特点。(奇怪的是,如果你用VB.NET,该属性是Overridable的,那么当你考虑到.NET Framework类库的跨语言一致性时,这个问题尤其严重。)唯一的解决办法是手动地编写Text属性初始化代码,或者用一个不同的属性名,如Title,就如样例代码所做的那样。
你的自定义控件继承自UserControl类,所以你需要为控件绘制边界。(如果我们继承Panel类,可能就不需要这么做了。)将绘制边界的代码放在Paint事件中:e.Graphics.DrawRectangle(System.Drawing.
Pens.Black, 0, 0, pageWidth - 1,
pageHeight - 1 );
如果你只需绘制边界,那么一切都会正常运行。然而,当你调整控件的大小使它变大时,你就会看到原有的边界仍然存在,因为对一个控件的最优化绘制使我们只可以绘制新的边界。你可以用Invalidate方法来克服这个问题,可以从Resize事件调用这个方法。该技巧类似于标准的Windows API编程。
[img]upload/2005-06/1_951.gif[/img]
图1. 聚合可缩进的控件
现在,你已经完成构建CollapsingControl了。然而,一个单独的CollapsingControl的用处并不大。只有将多个CollapsingControl同GroupingControl(见图1)一起使用时,你才会意识到该控件的真正价值。GroupingControl包含逻辑来调整每个CollapsingControl的面板的弹出和缩进。例子中的GroupingControl不是工业级(commercial-grade)的,但它说明了可用的功能。当将每个CollapsingControl添加到GroupingControl时,GroupingControl用事件来重新配置它们 (见列表2)。
当每个CollapsingControl弹出或缩进时,GroupingControl就会向上或向下移动其它CollapsingControl。每个CollapsingControl通过你在Padding属性中指定的边界与其相邻的CollapsingControl分开。Padding属性也提供了距离GroupingControl边缘的边界。如果GroupingControl中的其它CollapsingControl将属性AutoClose设置为true,那么GroupingControl就只允许弹出一个CollapsingControl。
即使掌握了我们给你提供的这些基本知识,你仍会发现开发自定义控件(如CollapsingControl)是个极大的挑战,因为你可能会碰到很多复杂情况,这是你在应用程序开发中不常遇到的。任何从事自定义控件开发的小组都应该拥有一些Microsoft Product Support Service以供查询。
当你准备从事另一个自定义控件项目时,你可能想对你刚刚在一个商业级控件中构建的可缩进的控件做些改进:你可以改进CollapsingControl使其变得更美观,用包含图片的按钮来替代LinkLabel,让用户配置控件的缩进高度,或者给Open和Close添加动画效果,使控件可以逐渐打开或关闭。你也可以激活GroupingControl中的Collapse All/Expand All,可以在设计时右击GroupingControl来添加CollapsingControl。另外还可以添加更多的AutoClose模式,如一个“Fit”模式,在这个模式下,只有在有必要避免溢出(overflow)时,控件才缩进。(你可以将GroupingControl的AutoScroll属性设置为true来适应溢出。)你也可以提供方法来改变GroupingControl中CollapsingControl的顺序。
关于作者:
Douglas Gennethen自1978年以来就是Hewlett-Packard的R&D工程师。他曾经在HP的Digital Imaging Operation从事模拟电路设计、成像、色彩和用户界面设计,现在主要致力于将.NET技术用于UI设计。他的联系方式是douglas.gennetten@hp.com。
Jonathan Lurie自1993年以来就在多个领域从事软件开发,包括会计学和法律学领域。他编写、教授软件开发方面的课程,并提供咨询服务。他拥有计算机科学的学士学位,并持有Java Programmer、IBM XML Developer、Solomon、MCT、MCDBA、MCSD、MCSE和MCF Instructor证书。他的联系方式是jonathanlurie@hotmail.com。
图片如下:

by Douglas Gennetten and Jonathan Lurie
技术工具箱:C#
在设计UI时,我们总是面临的一个很大的挑战就是在添加功能的同时不要破坏可用性。在Web上实现这一点的一个很流行的做法就是运用可缩进的面板(collapsible panel)——它包含控件,用户可以随意隐藏和显示它们。在缩进面板时,每个面板只呈现一个小的标题栏和一个用来展开它的小的按钮。你可以用类似的、用处更多的元素改进你的WinForms应用程序中的UI。
我们将讲述如何创建一个强大的WinForms复合控件,它可以实现一组可缩进的面板。样例代码提供了一个功能很全的控件,它可以让你创建灵活的UI。该控件不只是个你在Web上可以看到的典型的可缩进的面板,它还可以自动管理一组面板的行为。例如,当你将控件设置到最节省空间的(space-conserving)模式时,打开任何面板都会自动缩进以前打开的任何面板,从而总是占用最小的界面空间。在另一种模式下,只有在有必要保持你指定的面板组的最多数量时,面板才会自动缩进。
在开始创建复合控件时,先创建一个Windows Control Library。该项目中包含两个不同的用户控件:CollapsingControl,用来缩进和弹出一个面板;和GroupingControl,它可以容纳一个或多个CollapsingControl。我们先创建CollapsingControl(见列表1)。你必须用类的Designer特性来完成创建。
在C#中,我们用方括号来标记特性。在类声明前运用Designer特性: Designer("System.Windows.Forms.Design.
ParentControlDesigner,System.
Design", typeof(System.
ComponentModel.Design.IDesigner))]
没有这个特性,你就不能添加任何控件到CollapsingControl。(另一种可选的方法就是将Panel类作为一个子类。然而,我们并没有选用那种方法,因此我们可以向你展示如何从头开始创建。)
CollapsingControl包含一个面板,它的Dock属性设置为Top。该面板包含一个label和一个linkLabel。你可以用label来显示对CollapsingControl的说明,将linkLabel用作一个按钮,用户通过点击它来缩进(<<)和弹出(>>)控件。将label的dock属性设置为Fill,将linkLabel的dock属性设置为Right。
可缩进的控件有两个高度:当前高度和弹出高度。你可以用一个私有字段和公有属性来存储、设置这些值。Browsable特性可以让你从VS.NET IDE的Properties窗口设置该属性,虽然用这种方法有些多余,因为缺省情况下,Browsable特性设置为true。然而,如果你不想从Properties窗口得到这些属性,你可以用下面的代码:[System.ComponentModel.Browsable
(false)]
将缩进控件的高度设置为label的高度。这个最小的高度可以是一个公开的属性,我们可以在以后增加其高度。当前的实现方式需要你手动地设置CollapsingControl的HeightExpanded属性。你可能想改变这种情况,使该属性可以自动设置,但这么做会不让你将控件的最初高度设置为最小高度。对Opened属性的设置可以决定在设计和运行时应该将一个CollapsingControl设置为Collapsed还是Expanded(缺省设置)。
设置标题
下一步是设置CollapsingControl的标题,就是label显示的文本。实现该步骤需要一个迂回的方法。你不能在C#中覆盖一个用户控件的Text属性,这么做是不合适的,因为你可能用Text属性来设置控件中label的Text属性。如果你试图用Text属性上的新的操作符来迂回实现该步骤(这会隐藏基类的Text属性),你就会碰到一个有趣的问题:从Properties窗口设置Text属性不能生成代码来设置这个值。这似乎是IDE中,而不是语言中的一个bug或特点。(奇怪的是,如果你用VB.NET,该属性是Overridable的,那么当你考虑到.NET Framework类库的跨语言一致性时,这个问题尤其严重。)唯一的解决办法是手动地编写Text属性初始化代码,或者用一个不同的属性名,如Title,就如样例代码所做的那样。
你的自定义控件继承自UserControl类,所以你需要为控件绘制边界。(如果我们继承Panel类,可能就不需要这么做了。)将绘制边界的代码放在Paint事件中:e.Graphics.DrawRectangle(System.Drawing.
Pens.Black, 0, 0, pageWidth - 1,
pageHeight - 1 );
如果你只需绘制边界,那么一切都会正常运行。然而,当你调整控件的大小使它变大时,你就会看到原有的边界仍然存在,因为对一个控件的最优化绘制使我们只可以绘制新的边界。你可以用Invalidate方法来克服这个问题,可以从Resize事件调用这个方法。该技巧类似于标准的Windows API编程。
[img]upload/2005-06/1_951.gif[/img]
图1. 聚合可缩进的控件
现在,你已经完成构建CollapsingControl了。然而,一个单独的CollapsingControl的用处并不大。只有将多个CollapsingControl同GroupingControl(见图1)一起使用时,你才会意识到该控件的真正价值。GroupingControl包含逻辑来调整每个CollapsingControl的面板的弹出和缩进。例子中的GroupingControl不是工业级(commercial-grade)的,但它说明了可用的功能。当将每个CollapsingControl添加到GroupingControl时,GroupingControl用事件来重新配置它们 (见列表2)。
当每个CollapsingControl弹出或缩进时,GroupingControl就会向上或向下移动其它CollapsingControl。每个CollapsingControl通过你在Padding属性中指定的边界与其相邻的CollapsingControl分开。Padding属性也提供了距离GroupingControl边缘的边界。如果GroupingControl中的其它CollapsingControl将属性AutoClose设置为true,那么GroupingControl就只允许弹出一个CollapsingControl。
即使掌握了我们给你提供的这些基本知识,你仍会发现开发自定义控件(如CollapsingControl)是个极大的挑战,因为你可能会碰到很多复杂情况,这是你在应用程序开发中不常遇到的。任何从事自定义控件开发的小组都应该拥有一些Microsoft Product Support Service以供查询。
当你准备从事另一个自定义控件项目时,你可能想对你刚刚在一个商业级控件中构建的可缩进的控件做些改进:你可以改进CollapsingControl使其变得更美观,用包含图片的按钮来替代LinkLabel,让用户配置控件的缩进高度,或者给Open和Close添加动画效果,使控件可以逐渐打开或关闭。你也可以激活GroupingControl中的Collapse All/Expand All,可以在设计时右击GroupingControl来添加CollapsingControl。另外还可以添加更多的AutoClose模式,如一个“Fit”模式,在这个模式下,只有在有必要避免溢出(overflow)时,控件才缩进。(你可以将GroupingControl的AutoScroll属性设置为true来适应溢出。)你也可以提供方法来改变GroupingControl中CollapsingControl的顺序。
关于作者:
Douglas Gennethen自1978年以来就是Hewlett-Packard的R&D工程师。他曾经在HP的Digital Imaging Operation从事模拟电路设计、成像、色彩和用户界面设计,现在主要致力于将.NET技术用于UI设计。他的联系方式是douglas.gennetten@hp.com。
Jonathan Lurie自1993年以来就在多个领域从事软件开发,包括会计学和法律学领域。他编写、教授软件开发方面的课程,并提供咨询服务。他拥有计算机科学的学士学位,并持有Java Programmer、IBM XML Developer、Solomon、MCT、MCDBA、MCSD、MCSE和MCF Instructor证书。他的联系方式是jonathanlurie@hotmail.com。
图片如下:

回复Comments
作者:
{commentrecontent}