如何使用Windows Forms 2.0创建智能应用程序布局?
使用 Windows Forms 2.0 创建智能应用程序布局
摘要:学习如何使用 Windows Forms 2.0 中的新控件创建智能化和可扩展的应用程序布局。
从 Microsoft 下载中心下载 C# 和 Visual Basic 代码示例 (903 KB)。
简介
Microsoft Windows Forms 2.0 允许您以独特的方式组织应用程序的功能,以便于客户使用。通过使用新的控件(比如 ToolStrip、FlowLayoutPanel 和 TableLayoutPanel),您可以创建智能化和可扩展的应用程序布局。本文介绍四种应用程序布局:选项卡样式的工具条、Outlook 样式的工具条、可折叠菜单和飞出式面板。所有这些布局用 Windows Forms 2.0 创建起来都很简单。本文假定您了解 Windows Forms 的基础知识并熟悉 UserControls。
TabControl 的局限
最近,我想要构建一个可作为各种活动(比如锻炼或思考)的倒计时器的简单应用程序。核心功能是可在设定的时间(比如 30 分钟)结束后反复播放一种声音。我知道这个简单的应用程序应当由三个主要部分构成:启动时显示的“Home”(主)面板、在其上设置和运行倒计时时钟的“Countdown”(倒计时)面板和在其上设置自定义声音并选择是否要保存会话记录的“Settings”(设置)面板。
我认定最好的入手方法是使用基于选项卡的导航方法。我在三个不同的选项卡上定义了主要的功能区域。最初,在 Windows Forms 中免费提供的 TabControl 似乎是自然而然的选择。但问题来了。我不想让此应用程序看上去像一个普通 Windows 应用程序,采用其默认的灰色配色方案。我也不想使用默认情况下的顶部对齐的选项卡。我想让各个选项卡在 TabControl 的右侧向下延伸排列。我希望所有内容均为白色,没有分散注意力的边框,并且我想让三个选项卡从应用程序的顶部延伸到底部。
我很快发现 TabControl 不能满足我的要求。虽然 TabControl 支持右对齐和左对齐选项卡,但默认情况下它会垂直而非水平呈现文本。我发现,如果启用“视觉样式”,则侧向对齐的选项卡上根本不会呈现任何文本!
我发现,通过使用 owner-draw(自绘)功能,可以获得与我的需求接近的效果(有关详细信息,请参阅我在 Windows Forms Documentation Updates 博客上发布的 Windows Forms TabControl:Using Right-Aligned or Left-Aligned Tabs(英文))。不过,还有其他的障碍阻止我使用 TabControl。特别是,自绘功能只能重绘选项卡的内容。我无法自定义或完全消除选项卡上或选项卡面板周围的选项卡边框。这限制了我的自由发挥。更有甚者,选项卡本身的控件在您应用“视觉样式”时有时布局不正确。鉴于所有这些问题以及缺乏可自定义性,我不再热衷于使用 TabControl 了。
选项卡样式的工具条
我停下来考虑了一下,意识到另有一种更具创造性的方法可以解决这个问题。我记得在以前的摸索练习中感到 Windows Forms 的新的 ToolStrip 控件极其灵活。我能否使用它来实现恰如所需要的感观呢?
结果表明可以这样做,并且只需要极少量的编程工作。图 1 显示了最终的结果。
图 1. 完成后的应用程序(使用了选项卡样式的工具条)。
下面介绍我创建该应用程序的过程。主窗体拆分为两部分。左侧为我编写的名为“Slideshow”的自定义 UserControl(包括在 Countdown 示例中)。这只不过是一个“卖弄一下”的控件,它使用托管的 WebBrowser 包装控件在一系列使用动态 HTML 的图像之间淡入淡出。
对于本文,有趣之处在于右侧,我在右侧放置了一个 SplitContainer 控件。SplitContainer 也是 Windows Forms 中的一个新控件。它替代了旧的 Splitter 控件,为拆分面板的数量和方向性提供了更强的功能。SplitContainer 具有两个拆分面板,即 Panel1 和 Panel2。Panel1 是包含每个按钮的各个控件的内容面板。Panel2 包括允许在以下三个面板之间进行导航的按钮:Home、Countdown 和 Settings。Panel2 将承载 ToolStrip 控件。对于我的应用程序,我将 SplitContainer 上的 Orientation 属性保持为默认值 Vertical,然后在 Microsoft Visual Studio 中调整面板的大小,使左侧的面板比右侧的面板大。图 2 显示了向 Panel2 中添加 ToolStrip 控件之前的 Countdown 应用程序。
图 2. 具有一个自定义 Slideshow UserControl 和一个 SplitContainer 的 Countdown 应用程序。
添加自定义 Slideshow UserControl 和 SplitContainer 后,我向 Panel2 中添加了一个 ToolStrip 控件。默认情况下,ToolStrip 的 Dock 属性设置为 Top。这样做是有道理的;大多数应用程序默认情况下都使用 ToolStrip 来创建命令工具栏,比如在 Microsoft Word 和其他应用程序中那样。但对于我的应用程序,我需要将 Dock 属性设置为 Fill。我也不希望使用 ToolStrip 默认情况下使用的 Office 式的老套呈现方式,因此,我将 RenderMode 设置为 System,并将 BackColor 设置为 White。我将 GripStyle 设置为 Hidden,以便让用于抓取 ToolStrip 并将其重新定位到屏幕其他区域的手柄不可见。最后,由于我想让导航按钮从上至下排列,即其中一个位于另一个的上方,因此我将 LayoutStyle 的值改为 VerticalStackWithOverflow。
接下来就是真正的导航按钮了。由于我将有三个面板,因此我创建了三个按钮。我在可视设计器中使用 Items Collection Editor(项集合编辑器)在 ToolStrip 中创建了三个 ToolStripButton 对象。为了打开 Items Collection Editor,我单击 ToolStrip 的 Items 属性旁边的省略号按钮(要打开 Items Collection Editor,也可以单击 ToolStrip 的智能标记,然后在“ToolStrip Tasks”(ToolStrip 任务)菜单中单击“Edit Items”(编辑项))。
接下来,我需要设置按钮的感观样式。我用库存的照片为每个按钮创建了一个图像,并将图像的大小调整到相同的高度和宽度。我单击每个 ToolStripButton 的 Image 属性旁边的省略号按钮,并使用“Select Resource”(选择资源)对话框导入了每个按钮的图像。默认情况下,ToolStripButton 会将图像缩小到按钮的默认大校由于我需要实际大小的图像,因此我将每个按钮上的 ImageScaling 属性设置为 None。
我在每个 ToolStripButton 的 Text 属性中输入了相应的文本。为了使该文本显示在图像的下面,我不得不更改两个属性。首先,我需要将 DisplayStyle 属性设置为 ImageAndText。这使文本显示在图像上中间偏右的位置。为了使文本显示在图像的下方,我将 TextImageRelation 属性设置为 ImageAboveText。之后我更改了 ToolStripButton 的字体,以便让文本按照我的想法呈现。
我最后的一招是,决定让按钮在受到单击后显示为“选中”状态,以便用户可以一目了然地看到哪个模板当前处于活动状态(在图 1 中,通过第二个按钮周围出现的边框来表示选中)。这了实现这一目标,我将每个按钮的 CheckOnClick 属性设置为 True。
使工具条选项卡可以工作
这是使用该设计器所能实现的极限操作。您可以看出,我完成了大量工作却未编写一点代码。现在的任务是设计各个面板,并将所有一切合并在一起。对于选项卡样式的工具条,我设法实现了以下逻辑:
• |
应用程序启动时默认显示“Home”面板。 |
• |
单击 ToolStrip 按钮时进行检测。如果尚未显示与该按钮相对应的面板,则创建并显示该面板。 |
• |
取消选中以前选中的按钮。 |
我为每个面板单独创建了一个 UserControl:即分别为“Home”面板、主“Countdown”面板和“Settings”面板各创建了一个。我将这些面板分别命名为 CDHomePanel、CDCountdownPanel 和 CDSettingsPanel。
在实现了这些 UserControls 的空外壳后,我开始着手编写在 SplitContainer 的 Panel1 中显示它们的逻辑。由于我仅有三个面板,因此我可以为每个按钮都进行单独的事件处理程序硬编码,用以实例化及显示相应的面板。但这并不是一种扩展性良好的方法。这种方法对于三个按钮没有问题,但如果最终有六个按钮,情况又会如何呢?如果要在需要 10 个或 20 个按钮的更大应用程序中使用这种方法,情况又当如何呢?我决定只编写一个事件处理程序来处理所有按钮,而不对任何值进行硬编码。我想让代码足够抽象,以便将来添加其他按钮和其他面板时,可以尽可能少地更改 ToolStripButton 事件处理程序代码。
首先,我回到了设计器中,将每个 ToolStripButton 的 Tag 属性都设置为 UserControl 的名称,并以对应于该按钮的命名空间作为前缀。我之所以这样做,是为了使我的事件处理程序能够使用受到单击的按钮的 Tag(标记)属性来推断出应该实例化哪个 UserControl。例如,对于 CDHomePanel UserControl,由于它存在于命令空间 Countdown 中,因此其完全限定的名称 Countdown.CDHomePanel。我将 "Countdown.CDHomePanel" 指定给“Home”按钮的 Tag 属性。类似地,我将“Countdown”按钮的 Tag 属性设置为 "Countdown.CDCountdownPanel",将“Settings”按钮的 Tag 属性设置为 "Countdown.CDSettingsPanel"。
接下来,我添加了一些我的窗体将需要的用于事件处理程序的代码。由于我打算以动态方式实例化 UserControl 面板,因此我需要使用 System.Reflection 和 System.Runtime.Remoting 命名空间中所定义的类。我还定义了两个我的事件处理程序将会需要的类级别的私有变量:_CurrentControl,它是对用户当前可见面板的引用;_CurrentClickedButton,它是对与可见面板相对应的 ToolStripButton 的引用。
Imports System.Threading
Imports System.Drawing.Drawing2D
Imports System.Reflection
Imports System.Runtime.Remoting
Public Class Form1
Private _CurrentControl As Control
Private _CurrentClickedButton As ToolStripButton = Nothing
接下来,我向窗体的 Load 事件处理程序添加了代码,以便实例化“Home”面板、向用户显示该面板并用有效的对象引用初始化 _CurrentControl 和 _CurrentClickedButton 变量。由于我只需要在应用程序的生命周期内进行一次此操作,因此我直接创建了 CDHomePanel 的一个实例。实例化该面板后,我将其添加到了 SplitContainer 的 Panel1 中。
Dim HomePanel As New CDHomePanel()
HomePanel.Tag = "Countdown.CDHomePanel"
HomePanel.Dock = DockStyle.Fill
SplitContainer1.Panel1.Controls.Add(HomePanel)
_CurrentControl = HomePanel
_CurrentClickedButton = HomeButton
最后,我编写了一个名为 Navigate 的方法,它设计成从每个 ToolStripButton 的 MouseDown 事件进行调用。(我将在后面的切换面板时显示提示一节中解释为什么会将此代码分解到一个单独的方法中。)由于我是在 Visual Basic 中进行创作,因此可以使用 Handles 关键字来指定此事件处理程序适用于所有三个按钮。处理程序会查看 SplitContainer 的 Panel1 的 Controls 集合,并会查看是否已经创建与此按钮相对应的 UserControl。如果没有,它会将 ToolStripButton 的 Tag 属性传递给 AppDomain 类中定义的 CreateInstance 方法,从而进行创建。
Private Sub Navigate(ByVal sender As Object)
Dim _NewControl As Control
Dim _CurrentButton As ToolStripButton = CType(sender, ToolStripButton)
Dim _ControlName As String = _CurrentButton.Tag.ToString()
' 取消选中以前单击的按钮。
_CurrentClickedButton.Checked = False
_CurrentClickedButton = _CurrentButton
' 首先,确保它不是冗余事件 - 我们是否已经
' 显示该控件?
If (Not (_ControlName = _CurrentControl.Name)) Then
' 开始使用该控件,如果尚未定义,
' 则动态实例化该控件。
_NewControl = SplitContainer1.Panel1.Controls(_ControlName)
If (_NewControl Is Nothing) Then
' 未找到控件 -
本文地址:http://www.45fan.com/dnjc/68918.html