65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 入门 - 第 13 章:使用 Windows Forms 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (85投票s)

2001 年 10 月 16 日

93分钟阅读

viewsIcon

1268183

C# 应用程序中 Windows 窗体控件的使用介绍。

Sample Image
标题 C# 入门
作者 Marco Bellinaso, Ollie Cornes, David Espinosa, Zach Greenvoss, Jacob Hammer Pedersen, Christian Nagel, Jon D Reid, Matthew Reynolds, Morgan Skinner, Karli Watson, Eric White
出版社 Wrox
出版日期 2001 年 9 月
ISBN 1861004982
价格 美国 49.99美元
页数 1035

使用 Windows 窗体控件

近年来,Visual Basic 因其提供直观的窗体设计器和易于学习的编程语言,使程序员能够创建高度详细的用户界面,从而共同产生了可能是市面上最好的快速应用程序开发环境,而广受赞誉。Visual Basic 以及其他快速应用程序开发工具(如 Delphi)所做的事情之一,是提供对许多预制控件的访问,开发人员可以使用这些控件快速构建应用程序的用户界面 (UI)。

大多数 Visual Basic Windows 应用程序的核心是窗体设计器。您可以通过将控件从工具箱拖放到窗体上,将它们放置在程序运行时您希望它们所在的位置,然后双击控件以添加控件的处理程序来创建用户界面。Microsoft 开箱即用的控件以及可以以合理价格购买的自定义控件,为程序员提供了前所未有的可重用、经过彻底测试的代码池,只需单击鼠标即可访问。Visual Basic 的核心现在通过 Visual Studio.NET 可供 C# 程序员使用。

在 .NET 之前使用的大多数控件过去是,现在仍然是特殊的 COM 对象,称为 ActiveX 控件。它们通常能够在设计时和运行时自行渲染。每个控件都有许多属性,允许程序员进行一定程度的自定义,例如设置背景颜色、标题及其在窗体上的位置。我们将在本章中看到的控件具有与 ActiveX 控件相同的外观和感觉,但它们不是——它们是 .NET 程序集。然而,仍然可以使用为旧版 Visual Studio 设计的控件,但会产生少量性能开销,因为当您这样做时,.NET 必须包装控件。出于显而易见的原因,当他们设计 .NET 时,Microsoft 不希望使大量现有控件变得多余,因此为我们提供了使用旧控件的方法,即使未来的控件是作为纯 .NET 组件构建的。

这些 .NET 程序集可以以这样的方式设计,即您将能够在任何 Visual Studio 语言中使用它们,并且希望并且相信不断增长的组件行业将抓住机遇,并开始生产纯 .NET 组件。我们将在下一章中介绍如何自己创建控件。

第 21 章提供了 .NET 程序集的深入解释。如果您想了解有关程序集的更多信息,请参阅该章节。

我们已经在本书前面提供的示例中看到了窗体设计器的实际应用,尽管只是简要地。在本章中,我们将更仔细地研究它,特别是我们如何使用许多控件,所有这些控件都随 Visual Studio.NET 一起提供。在本书的范围内介绍 Visual Studio.NET 中存在的所有控件将是一项不可能完成的任务,因此我们将介绍最常用的控件,范围从标签和文本框到列表视图和状态栏。

Windows 窗体设计器

我们将从简要介绍 Windows 窗体设计器开始。这是您布局用户界面的主要场所。完全有可能在不使用 Visual Studio.NET 的情况下设计窗体,但在记事本中设计界面可能是一个相当痛苦的经历。

让我们看看我们将要使用的环境。启动 Visual Studio.NET 并通过选择“文件 | 新建 | 项目”来创建一个新的 C# Windows 应用程序项目。在出现的对话框中,单击左侧树中的“Visual C# 项目”,然后选择右侧列表中的“Windows 应用程序”。现在,只需使用 Visual Studio 建议的默认名称,然后单击“确定”。这应该会弹出一个类似于下面所示的窗口

如果您熟悉 Visual Basic 中找到的窗体设计器,您会注意到它们的相似之处——显然有人认为该设计器是一个赢家,并决定允许它在其他 Visual Studio 语言中使用。如果您不熟悉 Visual Basic 设计器,那么上面的屏幕截图中有很多事情正在发生,所以让我们花点时间一个一个地浏览这些面板。

屏幕中央是您正在设计的窗体。您可以将控件从工具箱拖放到窗体上。工具箱在上面的图片中是折叠的,但如果您将鼠标指针移动到屏幕最左侧的“工具箱”选项卡上,它将展开。然后,您可以单击面板右上角的图钉以将其固定。这将重新排列工作区,使工具箱始终位于顶部,并且不会遮挡窗体。我们稍后将仔细查看工具箱及其包含的内容。

左侧栏上折叠的还有“服务器资源管理器”——由工具箱选项卡顶部的计算机图标表示。您可以将其视为 Windows 控制面板的一个小版本。从这里,您可以浏览网络上的计算机,添加和删除数据库连接等等。

窗口右侧有两个面板。右上角的是“解决方案资源管理器”和“类视图”。在“解决方案资源管理器”中,您可以看到所有打开的项目及其关联的文件。通过单击“解决方案资源管理器”底部的选项卡,您可以激活“类查看器”。在此处,您可以浏览项目中的所有类以及它们派生自的所有类。

屏幕右下角是“属性”面板。此面板将包含所选项目的所有属性,以便于参考和编辑。本章我们将大量使用此面板。

在此面板中,还可见“动态帮助”选项卡。即使在您键入时,此面板也会为您提供任何选定对象和代码的帮助提示。如果您的计算机使用较旧的微处理器或 RAM 较少,那么我建议您在不需要时将其从面板中删除,因为所有这些搜索帮助都会使性能相当迟缓。

工具箱

让我们仔细看看工具箱。如果您尚未执行此操作,请将鼠标指针移动到屏幕左侧的工具箱上,并通过单击展开面板右上角的图钉将其固定到前台。

如果您不小心点击了 X 键从而移除了工具箱,您可以通过从“视图”菜单中选择“工具箱”或按下 Ctrl-Alt-X 来使其重新出现。

工具箱包含可供 .NET 开发人员使用的所有控件。特别是,它提供了对 Windows 应用程序开发人员很重要的选择。如果您选择创建一个 Web 窗体项目,而不是 Windows 应用程序,您将获得一个不同的工具箱。您不限于使用此选择。您可以自定义工具箱以满足您的需求,但在本章中,我们将重点介绍上面图片中显示的选择中的控件——事实上,我们将查看此处显示的大多数控件。

既然我们知道将在哪里工作,那么让我们看看一般的控件。

控件

.NET 中的大多数控件都派生自 System.Windows.Forms.Control 类。这个类定义了控件的基本功能,这就是为什么我们将看到的控件中的许多属性和事件都是相同的。这些类中的许多本身就是其他控件的基类,如下图所示的 Label 和 TextBoxBase 类就是这种情况

一些名为自定义或用户控件的控件派生自另一个类:System.Windows.Forms.UserControl。这个类本身派生自 Control 类,并提供我们自己创建控件所需的功能。我们将在第 14 章中介绍这个类。顺便说一下,用于设计 Web 用户界面的控件派生自另一个类,System.Web.UI.Control。

属性

所有控件都具有许多用于操作控件行为的属性。大多数控件的基类 Control 具有许多其他控件直接继承或覆盖以提供某种自定义行为的属性。

下表显示了 Control 类的一些最常见属性。这些属性将出现在本章中我们将访问的大多数控件中,因此,除非所讨论控件的属性行为发生更改,否则将不再详细解释它们。请注意,此表并非旨在详尽;如果您想查看该类的所有属性,请参阅 MSDN 库

名称

可用性

描述

Anchor

读/写

使用此属性,您可以指定控件在其容器调整大小时的行为方式。有关此属性的详细解释,请参阅下文。

BackColor

读/写

控件的背景颜色。

底部

读/写

通过设置此属性,您可以指定从窗口顶部到控件底部的距离。这与指定控件的高度不同。

Dock

读/写

允许您使控件停靠在窗口的边缘。有关此属性的更详细解释,请参阅下文。

Enabled

读/写

将 Enabled 设置为 true 通常表示控件可以接收来自用户的输入。将 Enabled 设置为 false 通常表示它不能。

ForeColor

读/写

控件的前景颜色。

高度

读/写

从控件顶部到底部的距离。

左侧

读/写

控件相对于窗口左边缘的左边缘。

名称

读/写

控件的名称。此名称可用于在代码中引用控件。

Parent

读/写

控件的父级。

右侧

读/写

控件相对于窗口左边缘的右边缘。

TabIndex

读/写

控件在其容器的 Tab 顺序中的编号。

TabStop

读/写

指定是否可以通过 Tab 键访问控件。

Tag

读/写

此值通常不被控件本身使用,而是用于您将有关控件的信息存储在控件本身上。当通过 Windows 窗体设计器为此属性赋值时,您只能为其分配字符串。

顶部

读/写

控件相对于窗口顶部的上边缘。

Visible

读/写

指定控件是否可见
在运行时。

宽度

读/写

控件的宽度。

Anchor 和 Dock 属性

这两个属性在设计表单时特别有用。确保窗口在用户决定调整窗口大小时不会变得一团糟远非易事,并且为此已经编写了无数行代码。许多程序通过简单地不允许窗口调整大小来解决问题,这显然是解决问题的最简单方法,但不是最好的。随 .NET 引入的 Anchor 和 Dock 属性让您无需编写一行代码即可解决此问题。

Anchor 属性用于指定当用户调整窗口大小时控件的行为方式。您可以指定控件是应该自行调整大小,按其自身边缘的比例锚定,还是保持相同大小,按其位置相对于窗口边缘锚定。

Dock 属性与 Anchor 属性相关。您可以使用它来指定控件应停靠在其容器的边缘。如果用户调整窗口大小,控件将继续停靠在窗口边缘。例如,如果您指定控件应停靠在其容器的底部,则控件将自行调整大小,始终占据屏幕底部,无论窗口如何调整大小。控件在此过程中不会调整大小;它只是保持停靠在窗口边缘。

有关 Anchor 属性的确切用法,请参阅本章后面的文本框示例。

事件

当用户单击按钮或按下按钮时,作为应用程序的程序员,您希望被告知此事。为此,控件使用事件。Control 类定义了本章中我们将使用的控件通用的一些事件。下表描述了其中一些事件。再次强调,这只是最常见事件的选集;如果您需要查看完整列表,请参阅 MSDN 库

名称

描述

单击

当控件被点击时发生。在某些情况下,当用户按下回车键时,此事件也会发生。

DoubleClick

当控件被双击时发生。处理某些控件(如 Button 控件)的 Click 事件意味着 DoubleClick 事件永远不会被调用。

DragDrop

当拖放操作完成时发生,换句话说,当一个对象被拖到控件上并且用户释放鼠标按钮时发生。

DragEnter

当正在拖动的对象进入控件的边界时发生。

DragLeave

当正在拖动的对象离开控件的边界时发生。

DragOver

当一个对象被拖到控件上时发生。

KeyDown

当控件获得焦点时,某个键被按下时发生。此事件总是在 KeyPress 和 KeyUp 之前发生。

名称

描述

KeyPress

当控件获得焦点时,某个键被按下时发生。此事件总是在 KeyDown 之后和 KeyUp 之前发生。KeyDown 和 KeyPress 的区别在于,KeyDown 传递被按下键的键盘代码,而 KeyPress 传递该键对应的字符值。

KeyUp

当控件获得焦点时,某个键被释放时发生。此事件总是在 KeyDown 和 KeyPress 之后发生。

GotFocus

当控件获得焦点时发生。不要使用此事件执行控件验证。请改用 Validating 和 Validated。

LostFocus

当控件失去焦点时发生。不要使用此事件执行控件验证。请改用 Validating 和 Validated。

MouseDown

当鼠标指针位于控件上方且鼠标按钮被按下时发生。这与 Click 事件不同,因为 MouseDown 在按钮被按下时立即发生,并在释放之前发生。

MouseMove

当鼠标在控件上方移动时持续发生。

MouseUp

当鼠标指针位于控件上方且鼠标按钮被
释放时发生。

Paint

当绘制控件时发生。

Validated

当 CausesValidation 属性设置为 true 的控件即将获得焦点时,会触发此事件。它在 Validating 事件完成后触发,并指示验证已完成。

Validating

当 CausesValidation 属性设置为 true 的控件即将获得焦点时触发。请注意,要验证的控件是失去焦点的控件,而不是获得焦点的控件。

我们将在本章其余部分的示例中看到许多这些事件。

现在我们准备开始查看控件本身,我们将从前面章节中看到的一个控件开始,即 Button 控件。

Button 控件

当你想到按钮时,你可能想到的是一个可以点击来执行某些任务的矩形按钮。然而,从技术上讲,Visual Studio.NET 中有三个按钮。这是因为单选按钮(顾名思义)和复选框也是按钮。因此,Button 类不是直接派生自 Control,而是派生自另一个名为 ButtonBase 的类,该类又派生自 Control。我们将在本节中重点介绍 Button 控件,并将单选按钮和复选框留待本章后面讨论。

你所能想到的几乎所有 Windows 对话框中都存在按钮控件。按钮主要用于执行三种任务

  • 关闭带有状态的对话框(例如,确定和取消按钮)
  • 对在对话框中输入的数据执行操作(例如,输入一些搜索条件后单击搜索)
  • 打开另一个对话框或应用程序(例如,帮助按钮)

使用按钮控件非常简单。它通常包括将控件添加到窗体并双击它以将代码添加到 Click 事件中,这对于您将处理的大多数应用程序来说可能已经足够了。

让我们看看控件的一些常用属性和事件。这将让您了解可以用它做什么。之后,我们将创建一个小示例来演示按钮的一些基本属性和事件。

按钮属性

我们将把这些属性列为按钮基类的成员,尽管从技术上讲它们是在 ButtonBase 基类中定义的。这里只解释最常用的属性。有关完整列表,请参阅 MSDN

名称

可用性

描述

FlatStyle

读/写

可以使用此属性更改按钮的样式。如果将样式设置为 PopUp,则按钮将显示为平面,直到用户将鼠标指针移到其上方。发生这种情况时,按钮将弹出到其正常的 3D 外观。

Enabled

读/写

我们将在此处提及它,尽管它派生自 Control,因为它对于按钮来说是一个非常重要的属性。将 Enabled 属性设置为 false 意味着按钮将变灰,并且在您单击它时不会发生任何事情。

Image

读/写

允许您指定将显示在按钮上的图像(位图、图标等)。

ImageAlign

读/写

使用此属性,您可以设置图像在按钮上显示的位置。

按钮事件

到目前为止,按钮最常用的事件是 Click 事件。当用户点击按钮时发生,我们指的是按下鼠标左键并在按钮上方再次释放它。这意味着,如果您左键单击按钮,然后将鼠标从按钮上移开,然后才释放,则 Click 事件将不会触发。此外,当按钮获得焦点且用户按下回车键时,Click 事件也会触发。如果您在窗体上有一个按钮,您应该始终处理此事件。

让我们转到示例。我们将创建一个带有三个按钮的对话框。其中两个按钮将把使用的语言从英语更改为丹麦语并返回(您可以随意使用您喜欢的任何语言)。最后一个按钮关闭对话框。

试试看 – 按钮测试

1.       打开 Visual Studio.NET 并创建一个新的 C# Windows 应用程序。将其命名为
应用程序 ButtonTest。

2.       将工具箱固定下来,然后双击“按钮”控件三次。然后移动按钮并调整窗体大小,如下图所示

3.       右键单击一个按钮并选择“属性”。然后通过在“属性”面板中选择“名称”编辑字段并键入文本,更改每个按钮的 Name 属性,如上图所示。

4.       将三个按钮的 Text 属性更改为与名称相同,但前三个字母(btn)除外。

5.       我们想在文本前面显示一个标志,以明确我们在谈论什么。选择“English”按钮并找到 Image 属性。单击其右侧的 (...) 以弹出一个对话框,您可以在其中选择图像。我们想要显示的标志图标随 Visual Studio.NET 一起提供。如果您安装到默认位置(在英语语言安装中),它们应该位于 C:\Program Files\Microsoft Visual Studio.NET\Common7\Graphics\icons\Flags。选择图标 flguk.ico。对丹麦语按钮重复此过程,选择 flgden.ico 文件(如果您想在此处使用不同的标志,则此目录将有其他标志可供选择)。

6.       此时您会注意到按钮文本和图标相互重叠,因此我们需要更改图标的对齐方式。对于“English”和“Danish”按钮,将 ImageAlign 属性更改为 MiddleLeft。

7.       此时,您可能需要调整按钮的宽度,使文本不会从图像结束的地方开始。通过选择每个按钮并拉出出现的右侧切口来执行此操作。

8.       最后,点击表单并将 Text 属性更改为“你会说英语吗?”

我们的对话框的用户界面就到此为止了。您现在应该有一个看起来像这样的东西

现在我们准备向对话框添加事件处理程序。双击“English”按钮。这将直接带您到事件处理程序。Click 事件是按钮的默认事件,当您双击按钮时会创建该事件。其他控件有其他默认值。

添加事件处理程序

当您双击控件时,窗体后面的代码中会发生两件事。首先,在 InitializeComponent() 方法中创建事件订阅。

this.btnEnglish.Click += new System.EventHandler(this.btnEnglish_Click);

如果您想订阅除默认事件之外的事件,您将需要自己编写订阅代码,我们将在本章的其余部分中这样做。重要的是要记住,InitializeComponent() 方法中的代码会在每次您从设计模式切换到代码时被覆盖。因此,您不应该在此方法中编写事件订阅。相反,请使用类的构造函数。

发生的第二件事是事件处理程序本身被添加

private void btnEnglish_Click(object sender, System.EventArgs e)
{
   this.Text = "Do you speak English?";
}

方法名是控件名、下划线和处理事件名的串联。第一个参数 object sender 将保存被点击的控件。在这个例子中,它将始终是方法名指示的控件,但在其他情况下,许多控件可能会使用相同的方法来处理事件,在这种情况下,您可以通过检查此值来确切地找出哪个控件正在调用。本章后面的文本框示例演示了如何为多个控件使用单个方法。另一个参数 System.EventArgs e 包含有关发生事件的信息。在这种情况下,我们不需要任何这些信息。

正如您在本书前面部分所回忆的,this 关键字标识了类的当前实例。由于我们正在处理的类由该实例表示,因此我们可以通过该关键字访问其包含的属性和控件。正如我们在上面的代码中所做的那样,在 this 上设置 Text 属性意味着我们正在设置窗体当前实例的 Text 属性。

返回到窗体设计器并双击“Danish”按钮,您将被带到该按钮的事件处理程序。代码如下

private void btnDanish_Click(object sender, System.EventArgs e)
{
   this.Text = "Taler du dansk?";
}
 

此方法与 btnEnglish_Click 相同,只是文本是丹麦语。最后,我们以与之前两次相同的方式为“确定”按钮添加事件处理程序。不过,代码略有不同

private void btnOK_Click(object sender, System.EventArgs e)
{
   Application.Exit();
}

至此,我们退出应用程序,同时也结束了第一个示例。编译并运行它,然后按几个按钮。您将获得类似于以下内容的输出

Label 和 LinkLabel 控件

Label 控件可能是所有控件中使用最广泛的。看看任何 Windows 应用程序,您会发现它们几乎出现在您能找到的任何对话框上。标签是一个简单的控件,只有一个目的:向用户显示标题或简短提示,以解释表单上的某些内容。

开箱即用的 Visual Studio.NET 包含两个 Label 控件,它们能够以两种不同的方式向用户展示自己

  • Label,标准的 Windows 标签
  • LinkLabel,一个类似于标准标签(并从中派生),但以互联网链接(超链接)的形式呈现

这两个控件位于“窗口窗体”选项卡上控制面板的顶部。在下图中,两种类型的 Label 各拖动了一个以说明两者外观上的差异

如果您有 Visual Basic 经验,您可能会注意到 Text 属性用于设置显示的文本,而不是 Caption 属性。您会发现所有内在的 .NET 控件都使用名称 Text 来描述控件的主要文本。在 .NET 之前,Caption 和 Text 可以互换使用。

这就是 Label 控件的大部分用法。通常,您不需要为标准 Label 添加任何事件处理代码。然而,对于 LinkLabel,如果您想允许用户点击它并将其带到文本中显示的网页,则需要一些额外的代码。

Label 控件有数量惊人的属性可以设置。其中大部分都派生自 Control,但也有一些是新增的。下表列出了最常见的属性。如果没有另外说明,这些属性在 Label 和 LinkLabel 控件中都存在

名称

可用性

描述

BorderStyle

读/写

允许您指定 Label 周围边框的样式。默认情况下没有边框。

DisabledLinkColor

读/写

(仅限 LinkLabel)用户单击后 LinkLabel 的颜色。

FlatStyle

读/写

控制控件的显示方式。将此属性设置为 PopUp 将使控件显示为平面,直到用户将鼠标指针移到控件上方。此时,控件将显示为凸起。

Image

读/写

此属性允许您指定一个图像(位图、图标等)以显示在标签中。

ImageAlign

读/写

(读/写)图像在标签中显示的位置。

LinkArea

读/写

(仅限 LinkLabel)文本中应显示为链接的范围。

LinkColor

读/写

(仅限 LinkLabel)链接的颜色。

链接

只读

(仅限 LinkLabel)LinkLabel 可以包含多个链接。此属性允许您找到所需的链接。控件会跟踪文本中显示的链接。

LinkVisited

只读

(仅限 LinkLabel)返回链接是否已被访问。

文本

读/写

标签中显示的文本。

TextAlign

读/写

文本在控件中的显示位置。

TextBox 控件

当您希望用户输入您在设计时不知道的文本(例如用户的姓名)时,应使用文本框。文本框的主要功能是供用户输入文本,但可以输入任何字符,并且完全可以强制用户只输入数字值。

开箱即用的 .NET 提供了两个基本控件来接收用户的文本输入:TextBox 和 RichTextBox(我们将在本章稍后讨论 RichTextBox)。这两个控件都派生自一个名为 TextBoxBase 的基类,该基类本身又派生自 Control。

TextBoxBase 为文本框中的文本操作提供了基本功能,例如选择文本、剪切和粘贴到剪贴板,以及各种事件。我们现在不专注于派生关系,而是先看两个控件中较简单的一个——TextBox。我们将构建一个示例来演示 TextBox 属性,并在此基础上稍后演示 RichTextBox 控件。

TextBox 属性

正如本章前面所述,属性太多,我们无法全部描述,因此此列表仅包含最常见的属性

名称

可用性

描述

CausesValidation

读/写

当一个具有此属性设置为 true 的控件即将获得焦点时,会触发两个事件:Validating 和 Validated。您可以处理这些事件以验证失去焦点的控件中的数据。这可能导致控件永远不会获得焦点。相关事件将在下面讨论。

CharacterCasing

读/写

一个值,指示 TextBox 是否更改输入的文本的大小写。可能
值是

q        Lower:文本框中输入的所有文本都转换为小写。

q        Normal:不做任何更改
文本。

q        Upper:文本框中输入的所有文本都转换为大写。

MaxLength

读/写

一个值,指定文本框中输入的所有文本的最大字符长度。如果最大限制仅受可用内存限制,则将此值设置为零。

Multiline

读/写

指示这是否是多行控件。多行控件能够显示多行文本。

PasswordChar

读/写

指定密码字符是否应替换单行文本框中输入的实际字符。如果 Multiline 属性为 true,则此操作无效。

ReadOnly

读/写

一个布尔值,指示文本是否只读。

ScrollBars

读/写

指定多行文本框是否应显示滚动条。

SelectedText

读/写

文本框中选定的文本。

SelectionLength

读/写

文本中选定的字符数。如果此值设置为大于文本中的总字符数,则控件会将其重置为总字符数减去 SelectionStart 的值。

SelectionStart

读/写

文本框中选定文本的起始位置。

WordWrap

读/写

指定多行文本框的行是否在超出控件宽度时自动换行。

TextBox 事件

仔细验证表单上的 TextBox 控件中的文本可以决定用户是满意还是非常生气。

您可能已经体验过,当对话框只有在您单击“确定”时才验证其内容时是多么烦人。这种验证数据的方法通常会导致显示一个消息框,通知您“文本框三号”中的数据不正确。然后,您可以继续单击“确定”,直到所有数据都正确。显然,这不是验证数据的好方法,那么我们能做什么呢?

答案在于处理 TextBox 控件提供的验证事件。如果您想确保文本框中未输入无效字符或只允许在特定范围内的值,那么您将需要向控件用户指示输入的值是否有效。

TextBox 控件提供以下事件(所有这些事件都继承自 Control)

名称

描述

Enter

GotFocus

Leave

Validating

Validated

LostFocus

这六个事件按照它们在此处列出的顺序发生。它们被称为“焦点事件”,每当控件焦点改变时都会触发,有两个例外。Validating 和 Validated 仅在接收焦点的控件的 CausesValidation 属性设置为 true 时才触发。接收控件触发事件的原因是,有时您不希望验证控件,即使焦点发生变化。一个例子是当用户单击“帮助”按钮时。

KeyDown

KeyPress

KeyUp

这三个事件被称为“按键事件”。它们允许您监控和更改输入到控件中的内容。

KeyDown 和 KeyUp 接收与按下的键对应的键代码。这允许您确定是否按下了 Shift 或 Control 和 F1 等特殊键。

另一方面,KeyPress 接收与键盘键对应的字符。这意味着字母“a”的值与字母“A”的值不同。如果您想排除一系列字符,例如只允许输入数字值,这将非常有用。

更改

无论发生什么变化,只要文本框中的文本发生变化,此事件就会发生。
变化。

试试看 – TextBoxTest

我们将创建一个对话框,您可以在其中输入您的姓名、地址、职业和年龄。此示例的目的是让您对操作属性和使用事件有良好的基础,而不是创建一个非常有用的东西。

我们首先构建用户界面

1.       选择“文件 | 新建”并在 C# 项目下创建一个新的 Windows 应用程序。将项目命名为 TextBoxTest。

2.       通过将标签、文本框和按钮拖到设计图面上来创建如下图所示的窗体。在调整两个文本框 txtAddress 和 txtOutput 的大小之前,您必须将其 Multiline 属性设置为 true。通过右键单击控件并选择“属性”来执行此操作

3.       按照上图所示命名控件。

4.       将每个文本框的 Text 属性设置为空字符串,这意味着应用程序首次运行时它们将不包含任何内容。

5.       将所有其他控件的 text 属性设置为与控件名称相同,但前三个字母除外。将窗体的 text 属性设置为图片中标题所示。

6.       将两个控件 txtOutput 和 txtAddress 的 Scrollbars 属性
设置为 Vertical。

7.       将 txtOutput 控件的 ReadOnly 属性设置为 true。

8.       将按钮 btnHelp 的 CausesValidation 属性设置为 false。回想一下关于 Validating 和 Validated 事件的讨论,将其设置为 false 将允许用户单击此按钮,而无需担心输入无效数据。

9.       当您将窗体调整到刚好包围控件时,是时候锚定控件,以便它们在窗体调整大小时表现正常。如下表所示设置 Anchor 属性

Control

锚定值

所有标签

上、下、左

所有文本框

上、下、左、右

两个按钮

上、右

提示:您可以从一个控件复制 Anchor 属性文本并粘贴到具有相同值的其他控件中。您无需使用下拉按钮。

txtOutput 被锚定而不是停靠在窗体底部的原因是,我们希望在拉动窗体时调整输出文本区域的大小。如果我们将控件停靠在窗体底部,它将移动以保持在底部,但不会调整大小。

10.  最后还需要设置一件事。在窗体上找到 Size 和 MinSize 属性。如果我们的窗体尺寸小于现在,则其意义不大,因此您应该将 MinSize 属性设置为与 Size 属性相同。

现在,设置表单的视觉部分的工作已经完成。如果你运行它,当你点击按钮或输入文本时什么都不会发生,但是如果你最大化或拉动对话框,控件会按照你在正确用户界面中想要的方式表现,保持原位并调整大小以填充整个对话框。如果你曾经尝试在 Visual Basic 6 这样的语言中完成相同的任务,你就会知道你刚刚节省了多少工作。

现在是时候查看代码了。右键单击窗体并选择“查看代码”。如果您已固定工具箱,则应取消固定以腾出更多代码窗口空间。

令人惊讶的是,编辑器中可见的代码很少。在我们的类顶部,控件被定义了,但直到您展开名为“Windows 窗体设计器生成的代码”的区域,您才能看到所有工作都去了哪里。应该强调的是,您绝不应该编辑此部分的代码!下次在设计器中更改某些内容时,它将被覆盖,甚至更糟的是,您可能会更改某些内容,导致窗体设计器无法再显示窗体。

您应该花一分钟时间查看此部分中的语句。您将确切地看到为什么可以在不使用 Visual Studio.NET 的情况下创建 Windows 应用程序。此部分中的所有内容都可以简单地在记事本或类似的文本编辑器中输入并编译。您还将看到为什么不建议这样做。在最好的情况下,跟踪这里的所有内容都是困难的;很容易引入错误,并且由于您无法看到所做操作的效果,因此在窗体上排列控件以使其看起来正确是一项繁琐的任务。然而,这确实为第三方软件生产商编写自己的编程环境以与 Visual Studio.NET 竞争打开了大门,因为用于创建窗体的编译器包含在 .NET 框架中,而不是 Visual Studio.NET 中。

添加事件处理程序

然而,在我们添加自己的代码之前,我们可以从窗体设计器中提取最后一点努力。返回到窗体设计器(通过单击文本编辑器顶部的选项卡),然后双击按钮 btnOK。对另一个按钮重复此操作。正如我们在本章前面按钮示例中看到的那样,这会导致为按钮的单击事件创建事件处理程序。当单击 OK 按钮时,我们希望将输入文本框中的文本传输到只读输出框中。

这是两个 click 事件的代码

private void btnOK_Click(object sender, System.EventArgs e)
{
   // No testing for invalid values are made, as that should
   // not be necessary

   string output;

   // Concatenate the text values of the four TextBoxes
   output = "Name: " + this.txtName.Text + "\r\n";
   output += "Address: " + this.txtAddress.Text + "\r\n";
   output += "Occupation: " + this.txtOccupation.Text + "\r\n";
   output += "Age: " + this.txtAge.Text;

   // Insert the new text
   this.txtOutput.Text = output;
}

private void btnHelp_Click(object sender, System.EventArgs e)
{
   // Write a short description of each TextBox in the Output TextBox
   string output;

   output = "Name = Your name\r\n;
   output += "Address = Your address\r\n";
   output += "Occupation = Only allowed value is 'Programmer'\r\n";
   output += "Age = Your age";

   // Insert the new text
   this.txtOutput.Text = output;
}

在两个函数中,都使用了文本框的 Text 属性,或者在 btnOK_Click() 函数中检索或设置,或者像 btnHelp_Click() 函数中那样简单设置。

我们插入用户输入的信息,而不费心检查其是否正确。这意味着我们必须在其他地方进行检查。在此示例中,需要满足一些条件才能使值正确

  • 用户的姓名不能为空
  • 用户的年龄必须是一个大于或等于零的数字
  • 用户的职业必须是“程序员”或留空
  • 用户的地址不能为空

由此可见,对两个文本框(txtName 和 txtAddress)需要进行的检查是相同的。我们还看到,我们应该阻止用户在 Age 框中输入任何无效内容,最后我们必须检查用户是否是程序员。

为了防止用户在输入任何内容之前单击“确定”,我们首先在窗体的构造函数中将“确定”按钮的 Enabled 属性设置为 false,确保在调用 InitializeComponent() 中生成的代码之后才设置该属性

public Form1()
{
   //
   // Required for Windows Form Designer support
   //
   InitializeComponent();
   this.btnOK.Enabled = false;
}

现在我们将为必须检查是否为空的两个文本框创建处理程序。我们通过订阅文本框的 Validating 事件来完成此操作。我们通知控件应由名为 txtBoxEmpty_Validating() 的方法处理该事件。

我们还需要一种方法来了解控件的状态。为此,我们使用文本框的 Tag 属性。如果您回忆本章前面关于此属性的讨论,我们说过只能从窗体设计器将字符串分配给 Tag 属性。但是,由于我们是从代码设置 Tag 值,因此我们可以随心所欲地使用它,并且在此处输入布尔值更合适。

在构造函数中,我们添加以下语句

this.btnOK.Enabled = false;

// Tag values for testing if the data is valid
this.txtAddress.Tag = false;
this.txtAge.Tag = false;
this.txtName.Tag = false;
this.txtOccupation.Tag = false;

// Subscriptions to events
this.txtName.Validating += new
       System.ComponentModel.CancelEventHandler(this.txtBoxEmpty_Validating);
this.txtAddress.Validating += new
       System.ComponentModel.CancelEventHandler(this.txtBoxEmpty_Validating);

如果您还不完全熟悉事件,请参阅第 12 章以获取完整的事件解释。

与我们之前看到的按钮事件处理程序不同,Validating 事件的事件处理程序是标准处理程序 System.EventHandler 的专用版本。此事件需要特殊处理程序的原因是,如果验证失败,则必须有一种方法来阻止任何进一步的处理。如果我们取消进一步的处理,那将意味着在输入的数据有效之前无法离开文本框。在此示例中,我们不会采取如此激烈的措施。

Validating 和 Validated 事件与 CausesValidation 属性结合使用,解决了在使用 GotFocus 和 LostFocus 事件执行控件验证时出现的一个棘手问题。这个问题发生在 GotFocus 和 LostFocus 事件持续触发时,因为验证代码试图在控件之间切换焦点,从而创建了一个无限循环。

我们添加事件处理程序如下

private void txtBoxEmpty_Validating(object sender,
                                    System.ComponentModel.CancelEventArgs e)
{
   // We know the sender is a TextBox, so we cast the sender object to that
   TextBox tb = (TextBox)sender;

   // If the text is empty we set the background color of the
   // Textbox to red to indicate a problem. We use the tag value
   // of the control to indicate if the control contains valid
   // information.
   if (tb.Text.Length == 0)
   {
      tb.BackColor = Color.Red;
      tb.Tag = false;
  
      // In this case we do not want to cancel further processing,
      // but if we had wanted to do this, we would have added this line:
      // e.Cancel = true;
   }
   else
   {
      tb.BackColor = System.Drawing.SystemColors.Window;
      tb.Tag = true;
   }

   // Finally, we call ValidateAll which will set the value of
   // the OK button.
   ValidateAll();
}

由于有多个文本框使用此方法处理事件,我们无法确定是哪个文本框调用了该函数。然而,我们知道,无论调用者是谁,调用该方法的效果都应该相同,因此我们可以简单地将 sender 参数强制转换为文本框并对其进行操作。

如果文本框中文本的长度为零,我们将其背景颜色设置为红色,并将标签设置为 false。如果不是,我们将其背景颜色设置为窗口的标准 Windows 颜色。

当您想在控件中设置标准颜色时,应始终使用 System.Drawing.SystemColors 枚举中的颜色。如果您只是将颜色设置为白色,那么如果用户更改了默认颜色设置,您的应用程序将看起来很奇怪。

ValidateAll() 函数将在本例末尾描述。

继续使用 Validating 事件,我们将添加的下一个处理程序是用于 Occupation 文本框的。过程与前两个处理程序完全相同,但验证代码不同,因为职业必须是 Programmer 或空字符串才有效。因此,我们在构造函数中添加了一个新行。

this.txtOccupation.Validating += new
     System.ComponentModel.CancelEventHandler(this.txtOccupation_Validating);

然后是处理程序本身

private void txtOccupation_Validating(object sender,
                                      System.ComponentModel.CancelEventArgs e)
{
   // Cast the sender object to a textbox
   TextBox tb = (TextBox)sender;

   // Check if the values are correct
   if (tb.Text.CompareTo("Programmer") == 0 || tb.Text.Length == 0)
   {
      tb.Tag = true;
      tb.BackColor = System.Drawing.SystemColors.Window;
   }
   else
   {
      tb.Tag = false;
      tb.BackColor = Color.Red;
   }

   // Set the state of the OK button
   ValidateAll();
}

我们倒数第二个挑战是年龄文本框。我们不希望用户输入任何非正数(包括 0 以简化测试)。为了实现这一点,我们将使用 KeyPress 事件在字符显示在文本框中之前删除任何不需要的字符。

首先,我们订阅 KeyPress 事件。我们像之前在构造函数中处理事件一样执行此操作

this.txtAge.KeyPress += new
           System.Windows.Forms.KeyPressEventHandler(this.txtAge_KeyPress);

此事件处理程序也是专门的。提供了 System.Windows.Forms.KeyPressEventHandler,因为事件需要有关所按键的信息。

然后我们添加事件处理程序本身

private void txtAge_KeyPress(object sender,
                             System.Windows.Forms.KeyPressEventArgs e)
{
   if ((e.KeyChar < 48 || e.KeyChar > 57) && e.KeyChar != 8)
      e.Handled = true; // Remove the character
}

字符 0 到 9 的 ASCII 值介于 48 和 57 之间,因此我们确保字符在此范围内。但是,我们有一个例外。ASCII 值 8 是退格键,出于编辑原因,我们允许它通过。

将 KeyPressEventArgs 的 Handled 属性设置为 true,表示控件不应再对字符做任何其他操作,因此该字符不会显示。

就目前而言,控件未被标记为无效或有效。这是因为我们需要另一次检查以查看是否输入了任何内容。这很简单,因为我们已经编写了执行此检查的方法,我们只需通过在构造函数中添加以下行来订阅 Age 控件的 Validating 事件

this.txtAge.Validating += new
       System.ComponentModel.CancelEventHandler(this.txtBoxEmpty_Validating);

所有控件还必须处理最后一个情况。如果用户在所有文本框中输入了有效文本,然后更改了某些内容,导致文本无效,则“确定”按钮仍保持启用状态。因此,我们必须为所有文本框处理最后一个事件处理程序:Change 事件,它将在任何文本字段包含无效数据时禁用“确定”按钮。

每当控件中的文本发生变化时,就会触发 Change 事件。我们通过在构造函数中添加以下行来订阅该事件

this.txtName.TextChanged += new System.EventHandler(this.txtBox_TextChanged);
this.txtAddress.TextChanged += new
                                System.EventHandler(this.txtBox_TextChanged);
this.txtAge.TextChanged += new System.EventHandler(this.txtBox_TextChanged);
this.txtOccupation.TextChanged += new
                               System.EventHandler(this.txtBox_TextChanged);

Change 事件使用我们从 Click 事件中了解的标准事件处理程序。最后,我们添加事件本身

private void txtBox_TextChanged(object sender, System.EventArgs e)
{
   // Cast the sender object to a Textbox
   TextBox tb = (TextBox)sender;

   // Test if the data is valid and set the tag and background
   // color accordingly.
   if (tb.Text.Length == 0 && tb != txtOccupation)
   {
      tb.Tag = false;
      tb.BackColor = Color.Red;
   }
   else if (tb == txtOccupation &&
             (tb.Text.Length != 0 && tb.Text.CompareTo("Programmer") != 0))
   {
      // Don't set the color here, as it will color change while the user
      // is typing
      tb.Tag = false;
   }
   else
   {
      tb.Tag = true;
      tb.BackColor = SystemColors.Window;
   }

   // Call ValidateAll to set the OK button
   ValidateAll();
}

这次,我们必须准确找出是哪个控件调用了事件处理程序,因为我们不希望当用户开始键入时,“职业”文本框的背景颜色变为红色。我们通过检查通过 sender 参数传递给我们的文本框的 Name 属性来完成此操作。

只剩下最后一件事:启用或禁用“确定”按钮的 ValidateAll 方法

private void ValidateAll()
{
   // Set the OK button to enabled if all the Tags are true
   this.btnOK.Enabled = ((bool)(this.txtAddress.Tag) &&
                         (bool)(this.txtAge.Tag) &&
                         (bool)(this.txtName.Tag) &&
                         (bool)(this.txtOccupation.Tag));
}

该方法简单地将“确定”按钮的 Enabled 属性值设置为 true,如果所有 Tag 属性都为 true。我们需要将 Tag 属性的值转换为布尔值,因为它存储为对象类型。

如果你现在测试程序,你应该会看到类似这样的东西

请注意,当您在包含无效数据的文本框中时,您可以单击“帮助”按钮,而背景颜色不会变为红色。

我们刚刚完成的例子与本章中您将看到的其他例子相比相当长。这是因为我们将在此例子的基础上进行构建,而不是重新发明轮子。

请记住,您可以从 www.wrox.com 下载本书示例的源代码。

RadioButton 和 CheckBox 控件

如前所述,RadioButton 和 CheckBox 控件与按钮控件共享其基类,尽管它们的外观和用途与按钮大相径庭。

单选按钮通常显示为一个标签,左侧带有一个圆点,可以选择或不选择。当您希望为用户提供几个互斥选项之间的选择时,应使用单选按钮。例如,如果您想询问用户的性别。

要将单选框分组在一起,使它们形成一个逻辑单元,您必须使用 GroupBox 控件。通过先在窗体上放置一个组框,然后将所需的 RadioButton 控件放置在组框的边界内,RadioButton 控件将知道更改其状态以反映组框内只能选择一个。如果您不将它们放置在组框内,则在任何给定时间只能选择窗体上的一个 RadioButton。

复选框通常显示为一个标签,左侧带有一个带勾选的小方框。当您希望允许用户选择一个或多个选项时,应使用复选框。例如,一份问卷询问用户尝试过哪些操作系统(例如,Windows 95、Windows 98、Linux、Max OS X 等)。

我们将查看这两个控件的重要属性和事件,从 RadioButton 开始,然后快速演示它们的用法。

RadioButton 属性

由于该控件派生自 ButtonBase,并且我们已经在前面的按钮示例中看到,因此只有少数属性需要描述。一如既往,如果您需要完整列表,请参阅 MSDN 库

名称

可用性

描述

外观

读/写

RadioButton 可以显示为带圆形复选框的标签,位于其左侧、中间或右侧,也可以显示为标准按钮。当它显示为按钮时,选中时将显示为按下状态,否则为 3D 状态。

AutoCheck

读/写

当此属性为 true 时,用户单击单选按钮时会显示复选标记。当它为 false 时,默认情况下不显示复选标记。

CheckAlign

读/写

使用此属性,您可以更改单选按钮的对齐方式。它可以是左、中、右。

Checked

读/写

指示控件的状态。如果控件有复选标记,则为 true,否则为 false。

RadioButton 事件

在使用 RadioButton 时,通常只使用一个事件,但一如既往,还有许多其他事件可以订阅。我们只在本章中介绍两个,之所以提及第二个事件,是因为两者之间存在一个微妙的差异,值得注意

名称

描述

CheckChanged

当 RadioButton 的选中状态改变时发送此事件。如果表单上或组框中有多个 RadioButton 控件,此事件将发送两次,首先发送给被选中但现在变为未选中的控件,然后发送给变为选中的控件。

单击

每次单击 RadioButton 时都会发送此事件。这与更改事件不同,因为连续单击 RadioButton 两次或多次只会更改 Checked 属性一次——并且仅当它尚未被选中时。

CheckBox 属性

正如您所想象的,此控件的属性和事件与 RadioButton 的非常相似,但有两个新的

名称

可用性

描述

CheckState

读/写

与 RadioButton 不同,CheckBox 可以有三种状态:Checked(选中)、Indeterminate(不确定)和 Unchecked(未选中)。当复选框的状态为 Indeterminate 时,标签旁边的控件通常显示为灰色,表示当前复选值无效或在当前情况下没有意义。这种状态的一个示例可以在您选择 Windows 资源管理器中的多个文件并查看其属性时看到。如果某些文件是只读的而另一些不是,则只读复选框将被选中但变灰——不确定。

ThreeState

读/写

当此属性为 false 时,用户将无法将 CheckBox 的状态更改为 Indeterminate。但是,您仍然可以通过代码将复选框的状态更改为 Indeterminate。

CheckBox 事件

您通常只在此控件上使用一个或两个事件。请注意,尽管 CheckChanged 事件同时存在于 RadioButton 和 CheckBox 控件上,但这些事件的效果不同

名称

描述

CheckedChanged

每当复选框的 Checked 属性更改时发生。请注意,在 ThreeState 属性为 true 的 CheckBox 中,可以单击复选框而不更改 Checked 属性。当复选框从选中状态更改为不确定状态时会发生这种情况。

CheckedStateChanged

当 CheckedState 属性更改时发生。由于 Checked 和 Unchecked 都是 CheckedState 属性的可能值,因此每当 Checked 属性更改时都会发送此事件。此外,当状态从 Checked 更改为 Indeterminate 时,也会发送此事件。

至此,RadioButton 和 CheckBox 控件的事件和属性就介绍完了。但在我们查看使用这些控件的示例之前,让我们先看看前面提到的 GroupBox 控件。

GroupBox 控件

在继续示例之前,我们将看看组框控件。此控件通常与 RadioButton 和 CheckBox 控件结合使用,以围绕一系列以某种方式逻辑链接的控件显示边框和标题。

使用组框就像将其拖到窗体上一样简单,然后将它应该包含的控件拖到它上面(但不能反过来——你不能将一个组框覆盖在一些预先存在的控件上)。这样做的结果是,控件的父级变成了组框,而不是窗体,因此可以在任何给定时间选择多个 RadioButton。但是,在组框内,只能选择一个 RadioButton。

父子关系可能需要进一步解释一下。当一个控件放置在窗体上时,该窗体被称为该控件的父级,因此该控件是该窗体的子级。当您将一个 GroupBox 放置在窗体上时,它就成为该窗体的子级。由于 GroupBox 本身可以包含控件,因此它成为这些控件的父级。这样做的好处是,移动 GroupBox 会移动其上放置的所有控件。

将控件放置在组框上的另一个效果是,它允许您通过简单地设置组框上的相应属性来更改其包含的所有控件的某些属性。例如,如果您想禁用组框中的所有控件,您可以简单地将组框的 Enabled 属性设置为 false。

我们将在以下示例中演示 GroupBox 的用法。

试试看 – 单选按钮和复选框示例

我们将修改演示文本框用法时使用的示例。在该示例中,唯一的可能职业是程序员。为了避免强制用户在他是程序员时编写此内容,我们将把这个文本框更改为复选框。

为了演示 RadioButton 的用法,我们将要求用户提供更多信息:他的性别。

将文本框示例更改如下

1.       移除名为 lblOccupation 的标签和名为 txtOccupation 的文本框。

2.       调整窗体大小以容纳一个带有用户性别信息的组框,并如下图所示命名新控件

3.       RadioButton 和 CheckBox 控件的文本应与控件的名称相同,但前三个字母除外。

4.       将 chkProgrammer 复选框的 Checked 属性设置为 true。

5.       将 rdoMale 或 rdoFemale 的 Checked 属性设置为 true。请注意,您不能同时将两者都设置为 true。如果您尝试这样做,另一个 RadioButton 的值将自动更改为 false。

示例的视觉部分不需要再做任何事情,但是代码中有一些更改。首先,我们需要删除所有对已删除文本框的引用。转到代码并完成以下步骤。

6.       在窗体的构造函数中,删除引用 txtOccupation 的两行。这包括对 Validating 和 TextChanged 事件的订阅以及将 txtBox 的标记设置为 false 的行。

7.       完全移除方法 txtOccupation_Validating。

此示例可在本章的代码下载中找到,作为 RadioButtonAndCheckBox Visual Studio.NET 项目。

添加事件处理程序

txtBox_TextChanged 方法包含检查调用控件是否为 txtOccupation TextBox 的测试。我们现在确信它不会是,因此我们通过删除 elseif 块并修改 if 测试如下来更改该方法

private void txtBox_TextChanged(object sender, System.EventArgs e)
{
   // Cast the sender object to a Textbox
   TextBox tb = (TextBox)sender;

   // Test if the data is valid and set the tag background
   // color accordingly.
   if (tb.Text.Length == 0)
   {
      tb.Tag = false;
      tb.BackColor = Color.Red;
   }
   else
   {
      tb.Tag = true;
      tb.BackColor = SystemColors.Window;
   }

   // Call ValidateAll to set the OK button
   ValidateAll();
}

我们检查已移除文本框值的最后一个位置是在 ValidateAll() 方法中。完全移除该检查,因此代码变为

private void ValidateAll()
{
   // Set the OK button to enabled if all the Tags are true
   this.btnOK.Enabled = ((bool)(this.txtAddress.Tag) &&
      (bool)(this.txtAge.Tag) &&
      (bool)(this.txtName.Tag));
}

由于我们使用的是复选框而不是文本框,我们知道用户无法输入任何无效信息,因为他或她始终是程序员或不是。

我们还知道用户是男性或女性,因为我们将其中一个 RadioButton 的属性设置为 true,从而阻止用户选择无效值。因此,唯一剩下的就是更改帮助文本和输出。我们在按钮事件处理程序中执行此操作

private void btnHelp_Click(object sender, System.EventArgs e)
{
   // Write a short descrption of each TextBox in the Output TextBox
   string output;

   output = "Name = Your name\r\n";
   output += "Address = Your address\r\n";
   output += "Programmer = Check 'Programmer' if you are a programmer\r\n";
   output += "Sex = Choose your sex\r\n";
   output += "Age = Your age";

   // Insert the new text
   this.txtOutput.Text = output;
}

只有帮助文本发生了变化,所以帮助方法中没有什么意外。在 OK 方法中它变得稍微有趣一些

private void btnOK_Click(object sender, System.EventArgs e)
{
   // No testing for invalid values are made, as that should
   // not be neccessary

   string output;

   // Concatenate the text values of the four TextBoxes
   output = "Name: " + this.txtName.Text + "\r\n";
   output += "Address: " + this.txtAddress.Text + "\r\n";
   output += "Occupation: " + (string)(this.chkProgrammer.Checked ?
            "Programmer" : "Not a programmer") + "\r\n";
   output += "Sex: " + (string)(this.rdoFemale.Checked ? "Female" :
                                                         "Male") + "\r\n";
   output += "Age: " + this.txtAge.Text;

   // Insert the new text
   this.txtOutput.Text = output;
}

突出显示的新行的第一行是打印用户职业的行。我们检查复选框的 Checked 属性,如果为 true,我们写入字符串 Programmer。如果为 false,我们写入 Not a programmer。

第二行只检查单选按钮 rdoFemale。如果该控件的 Checked 属性为 true,则表示用户是女性。如果为 false,则表示用户是男性。由于这里只有两个选项,我们不需要检查另一个单选按钮,因为它的 Checked 属性将始终与第一个单选按钮相反。

如果使用了两个以上的单选按钮,我们就必须遍历所有按钮,直到找到一个 Checked 属性为 true 的按钮。

现在运行示例,您应该能够获得类似以下的结果

RichTextBox 控件

与普通的 TextBox 一样,RichTextBox 控件派生自 TextBoxBase。因此,它与 TextBox 共享许多功能,但功能更加多样。TextBox 通常用于从用户那里获取短文本字符串,而 RichTextBox 则用于显示和输入格式化文本(例如粗体下划线斜体)。它通过使用一种称为富文本格式或 RTF 的格式化文本标准来实现这一点。

在前面的示例中,我们使用了标准的 TextBox。我们同样可以使用 RichTextBox 来完成这项工作。事实上,正如我们稍后的示例将看到的,您可以删除 TextBox 名称 txtOutput,并用一个同名的 RichTextBox 代替它,示例的行为将与之前完全相同。

RichTextBox 属性

如果这种文本框比我们上一节探讨的文本框更高级,您会期望有新的属性可以使用,您是对的。以下是 RichTextBox 最常用属性的描述

名称

可用性

描述

CanRedo

只读

如果已撤消的内容可以重新应用,则此属性为
true,否则为 false。
否则为 false。

CanUndo

只读

如果可以在 RichTextBox 上执行撤消操作,则此属性为 true,否则为 false。

RedoActionName

只读

此属性保存一个操作的名称,该操作可用于重做 RichTextBox 中已撤消的内容。

DetectUrls

读/写

将此属性设置为 true,使控件检测 URL 并对其进行格式化(如下划线,如
浏览器中)。

Rtf

读/写

这对应于 Text 属性,只不过它以 RTF 格式保存文本。

SelectedRtf

读/写

使用此属性以 RTF 格式获取或设置控件中选定的文本。例如,如果您将此文本复制到另一个应用程序(例如 MS Word),它将保留所有格式。

SelectedText

读/写

与 SelectedRtf 一样,您可以使用此属性获取或设置选定的文本。然而,与 RTF 版本的属性不同,所有格式都将丢失。

SelectionAlignment

读/写

这表示选定文本的对齐方式。它可以是 Center、Left 或 Right。

SelectionBullet

读/写

使用此属性查找选定内容前面是否带有项目符号,或使用它插入或删除项目符号。

BulletIndent

读/写

使用此属性指定项目符号应缩进的像素数。

SelectionColor

读/写

允许您更改选定内容中文本的颜色。
选定内容。

SelectionFont

读/写

允许您更改选定内容中文本的字体。

SelectionLength

读/写

使用此属性,您可以设置或检索选定内容的长度。

SelectionType

只读

此属性保存有关选定内容的信息。它将告诉您是否选择了一个或多个 OLE 对象,或者是否只选择了文本。

ShowSelectionMargin

读/写

如果将此属性设置为 true,则 RichTextBox 的左侧将显示一个边距。这将使用户更容易选择文本。

UndoActionName

只读

获取用户选择撤消某个操作时将使用的操作名称。

SelectionProtected

读/写

通过将此属性设置为 true,您可以指定文本的某些部分不应更改。
设置为 true。

正如您从上面的列表中可以看到的,大多数新属性都与选定内容有关。这是因为,当用户处理其文本时,您将应用的任何格式可能都会作用于用户所做的选定内容。如果未进行选定,则格式将从文本中光标所在的位置(称为插入点)开始。

RichTextBox 事件

RichTextBox 使用的大多数事件与 TextBox 使用的事件相同。然而,有一些新的有趣的事件

名称

描述

LinkedClick

当用户点击文本中的链接时,会发送此事件。

Protected

当用户尝试修改标记为受保护的文本时,会发送此事件。

SelectionChanged

当选定内容更改时,会发送此事件。如果由于某种原因您不希望用户更改选定内容,您可以在此处阻止更改。

尝试一下 – RichTextBox 示例

在此示例中,我们将创建一个非常基本的文本编辑器。它演示了如何更改文本的基本格式以及如何从 RichTextBox 加载和保存文本。为简单起见,该示例从固定文件加载和保存。

一如既往,我们将从设计窗体开始

1.        创建一个新的 C# Windows 应用程序项目并将其命名为 RichTextBoxTest。

2.        创建如下图所示的窗体。名为 txtSize 的文本框应为 TextBox 控件。名为 rtfText 的文本框应为 RichTextBox 控件。

3.        按照上图所示命名控件,并清除 rtfText 和 txtSize 的 Text 属性。

4.        除了文本框,将所有控件的 Text 属性设置为与名称相同,但前三个字母除外。

5.        将 txtSize 文本框的 Text 属性更改为 10。

6.        按照下表定位控件

控件名称

锚定值

btnLoad 和 btnSave

底部

RtfText

顶部、左侧、底部、右侧

所有其他

顶部

7.        将窗体的 MinimumSize 属性设置为与 Size 属性相同。

添加事件处理程序

示例的视觉部分到此结束,我们将直接进入代码。双击“粗体”按钮以将 Click 事件处理程序添加到代码中。以下是该事件的代码

private void btnBold_Click(object sender, System.EventArgs e)
{
   Font oldFont;
Font newFont;

   // Get the font that is being used in the selected text
   oldFont = this.rtfText.SelectionFont;

   // If the font is using bold style now, we should remove the
   // Formatting
   if (oldFont.Bold)
      newFont = new Font(oldFont, oldFont.Style & ~FontStyle.Bold);
   else
      newFont = new Font(oldFont, oldFont.Style | FontStyle.Bold);

   // Insert the new font and return focus to the RichTextBox
   this.rtfText.SelectionFont = newFont;
   this.rtfText.Focus();
}

我们首先获取当前选定内容中使用的字体并将其分配给局部变量。然后我们检查此选定内容是否已设置为粗体。如果是,我们希望删除粗体设置;否则,我们希望设置它。我们使用 oldFont 作为原型创建一个新字体,但根据需要添加或删除粗体样式。

最后,我们将新字体分配给选定内容,并将焦点返回到 RichTextBox。

有关 Font 对象的描述,请参阅第 16 章。

btnItalic 和 btnUnderline 的事件处理程序与上面相同,只是我们正在检查相应的样式。双击斜体和下划线这两个按钮并添加此代码

private void btnItalic_Click(object sender, System.EventArgs e)
{
   Font oldFont;
   Font newFont;

   // Get the font that is being used in the selected text
   oldFont = this.rtfText.SelectionFont;

   // If the font is using Italic style now, we should remove it
   if (oldFont.Italic)
      newFont = new Font(oldFont, oldFont.Style & ~FontStyle.Italic);
   else
      newFont = new Font(oldFont, oldFont.Style | FontStyle.Italic);

   // Insert the new font
   this.rtfText.SelectionFont = newFont;
   this.rtfText.Focus();
}

private void btnUnderline_Click(object sender, System.EventArgs e)
{
   Font oldFont;
   Font newFont;
   // Get the font that is being used in the selected text
   oldFont = this.rtfText.SelectionFont;

   // If the font is using Underline style now, we should remove it
   if (oldFont.Underline)
      newFont = new Font(oldFont, oldFont.Style & ~FontStyle.Underline);
   else
      newFont = new Font(oldFont, oldFont.Style | FontStyle.Underline);

   // Insert the new font
   this.rtfText.SelectionFont = newFont;
   this.rtfText.Focus();
}

双击最后一个格式按钮“居中”,并添加以下代码

private void btnCenter_Click(object sender, System.EventArgs e)
{
   if (this.rtfText.SelectionAlignment == HorizontalAlignment.Center)
      this.rtfText.SelectionAlignment = HorizontalAlignment.Left;
   else
      this.rtfText.SelectionAlignment = HorizontalAlignment.Center;
   this.rtfText.Focus();
}

在这里,我们必须检查另一个属性 SelectionAlignment,以查看选定文本是否已居中。HorizontalAlignment 是一个枚举,可以是 Left、Right、Center、Justify 和 NotSet。在这种情况下,我们只需检查是否设置了 Center,如果设置了,则将对齐方式设置为 Left。如果未设置,则将其设置为 Center。

我们的小文本编辑器将能够执行的最后一种格式是设置文本大小。我们将为文本框大小添加两个事件处理程序,一个用于控制输入,一个用于检测用户何时完成输入值。

将以下行添加到窗体的构造函数中

public Form1()
{
   InitializeComponent();

   // Event Subscription
   this.txtSize.KeyPress += new
      System.Windows.Forms.KeyPressEventHandler(this.txtSize_KeyPress);
   this.txtSize.Validating += new
      System.ComponentModel.CancelEventHandler(this.txtSize_Validating);
}

我们在前面的示例中看到了这两个事件处理程序。这两个事件都使用了一个名为 ApplyTextSize 的辅助方法,该方法接受一个带有文本大小的字符串。

private void txtSize_KeyPress(object sender,
                              System.Windows.Forms.KeyPressEventArgs e)
{
   // Remove all characters that are not numbers, backspace and enter
   if ((e.KeyChar < 48 || e.KeyChar > 57) &&
                                          e.KeyChar != 8 && e.KeyChar != 13)
   {
      e.Handled = true;
   }
   else if (e.KeyChar == 13)
   {
      // Apply size if the user hits enter
      TextBox txt = (TextBox)sender;

      if (txt.Text.Length > 0)
         ApplyTextSize(txt.Text);
      e.Handled = true;
      this.rtfText.Focus();
   }
}

private void txtSize_Validating(object sender,
                                System.ComponentModel.CancelEventArgs e)
{
   TextBox txt = (TextBox)sender;

   ApplyTextSize(txt.Text);
   this.rtfText.Focus();
}

private void ApplyTextSize(string textSize)
{
   // Convert the text to a float because we'll be needing a float shortly
   float newSize = Convert.ToSingle(textSize);
   FontFamily currentFontFamily;
   Font newFont;

   // Create a new font of the same family but with the new size
   currentFontFamily = this.rtfText.SelectionFont.FontFamily;
   newFont = new Font(currentFontFamily, newSize);

   // Set the font of the selected text to the new font
   this.rtfText.SelectionFont = newFont;
}

我们感兴趣的工作发生在辅助方法 ApplyTextSize 中。它首先将大小从字符串转换为浮点数。我们阻止用户输入除整数之外的任何内容,但是当我们创建新字体时,我们需要一个浮点数,因此将其转换为正确的类型。

之后,我们获取字体所属的字体族,并从该字体族中创建一个具有新大小的新字体。最后,我们将选定内容的字体设置为新字体。

这就是我们能做的所有格式化,但有些是由 RichTextBox 本身处理的。如果您现在尝试运行该示例,您将能够将文本设置为粗体、斜体和下划线,并且可以使文本居中。这是您所期望的,但还有一些有趣的地方。尝试在文本中键入一个网址,例如 www.wrox.com。该文本被控件识别为 Internet 地址,会带下划线,当您将鼠标指针移到文本上方时,鼠标指针会变为手形。如果这让您相信您可以单击它并跳转到页面,那么您几乎是正确的。我们需要处理当用户单击链接时发送的事件:LinkClicked。

我们通过像现在习惯的那样在构造函数中订阅事件来做到这一点

this.rtfText.LinkClicked += new
      System.Windows.Forms.LinkClickedEventHandler(this.rtfText_LinkedClick);

我们以前没有见过这个事件处理程序。它用于提供被点击链接的文本。处理程序出奇地简单,看起来像这样

private void rtfText_LinkedClick(object sender,  
                                 System.Windows.Forms.LinkClickedEventArgs e)
{
   System.Diagnostics.Process.Start(e.LinkText);
}

此代码会打开默认浏览器(如果尚未打开),并导航到被点击链接指向的网站。

应用程序的编辑部分现已完成。剩下的就是加载和保存控件的内容。我们将使用一个固定文件来完成此操作。

双击“加载”按钮,并添加以下代码

private void btnLoad_Click(object sender, System.EventArgs e)
{
   // Load the file into the RichTextBox
   try
   {
      rtfText.LoadFile("../../Test.rtf");
   }
   catch (System.IO.FileNotFoundException)
   {
      MessageBox.Show("No file to load yet");
   }
}

就是这样!无需再做其他事情。因为我们正在处理文件,所以总有可能遇到异常,我们必须处理这些异常。在 Load 方法中,我们处理文件不存在时抛出的异常。保存文件同样简单。双击 Save 按钮并添加此代码

private void btnSave_Click(object sender, System.EventArgs e)
{
   // Save the text
   try
   {
      rtfText.SaveFile("../../Test.rtf");
   }
   catch (System.Exception err)
   {
      MessageBox.Show(err.Message);
   }
}

现在运行示例,格式化一些文本,然后单击“保存”。清除文本框并单击“加载”,您刚刚保存的文本应该会重新出现。

RichTextBox 示例到此结束。运行它时,您应该能够生成类似这样的内容

ListBox 和 CheckedListBox 控件

列表框用于显示字符串列表,一次可以从中选择一个或多个。就像复选框和单选按钮一样,列表框提供了一种让用户进行一个或多个选择的方式。当您在设计时不知道用户可以从(例如,同事列表)实际选择的值的数量时,应该使用列表框。即使您在设计时知道所有可能的值,如果值数量很多,也应该考虑使用列表框。

ListBox 类派生自 ListControl 类,后者为 Visual Studio.NET 中自带的两种列表类型控件提供了基本功能。另一个控件 ComboBox 将在本章后面讨论。

Visual Studio.NET 提供了另一种列表框。它叫做 CheckedListBox,派生自 ListBox 类。它提供了一个与 ListBox 类似的列表,但除了文本字符串之外,它还为列表中的每个项目提供一个复选框。

ListBox 属性

除非明确说明,否则下表中的所有属性都存在于 ListBox 类和 CheckedListBox 类中。

名称

可用性

描述

SelectedIndex

读/写

此值表示列表框中选定项的从零开始的索引。如果列表框可以同时包含多个选定内容,则此属性保存选定列表中第一个项的索引。

ColumnWidth

读/写

在多列列表框中,此属性指定列的宽度。

Items

只读

Items 集合包含列表框中的所有项目。您可以使用此集合的属性来添加和删除项目。

MultiColumn

读/写

列表框可以有多个列。使用此属性获取或设置列表框中的列数。

SelectedIndices

只读

此属性是一个集合,它包含列表框中选定项目的从零开始的所有索引。

SelectedItem

读/写

在只能选择一个项目的列表框中,此属性包含(如果有)选定的项目。在可以进行多个选择的列表框中,它将包含选定项目中的第一个。

SelectedItems

只读

此属性是一个集合,其中包含所有当前选定的项目。

SelectionMode

读/写

您可以在列表框中选择四种不同的选择模式

q         None: 无法选择任何项目。

q         One: 任何时候只能选择一个项目。

q         MultiSimple: 可以选择多个项目。

q         MultiExtended: 可以选择多个项目,用户可以使用 Ctrl、Shift 和箭头键进行选择。

已排序

读/写

将此属性设置为 true 将使 ListBox 按照字母顺序对它包含的项目进行排序。

文本

读/写

我们已经在许多控件上看到了 Text 属性,但这个属性的工作方式与我们目前所见到的任何属性都大不相同。如果您设置列表框控件的 Text 属性,它会搜索与文本匹配的项目并将其选中。如果您获取 Text 属性,返回的值将是列表中第一个选中的项目。如果 SelectionMode 为 None,则不能使用此属性。

CheckedIndicies

只读

(仅限 CheckedListBox)此属性是一个集合,其中包含处于选中或不确定状态的 CheckedListBox 中的所有索引。

CheckedItems

只读

(仅限 CheckedListBox)这是一个 CheckedListBox 中所有处于选中或不确定状态的项目的集合。

CheckOnClick

读/写

(仅限 CheckedListBox)如果此属性为 true,则当用户单击项目时,项目将更改其状态。

ThreeDCheckBoxes

读/写

(仅限 CheckedListBox)您可以通过设置此属性来选择平面或常规复选框。
此属性。

ListBox 方法

为了高效地使用列表框,您应该了解一些可以调用的方法。下表列出了最常用的方法。除非另有说明,否则这些方法属于 ListBox 和 CheckedListBox 类

名称

描述

ClearSelected

清除 ListBox 中的所有选定内容。

FindString

查找 ListBox 中以您指定的字符串开头的第一个字符串,例如 FindString("a") 将查找 ListBox 中以 'a' 开头的第一个字符串

FindStringExact

与 FindString 类似,但必须完全匹配整个字符串

GetSelected

返回一个值,指示项目是否被选中

SetSelected

设置或清除项目的选择

ToString

返回当前选定的项目

GetItemChecked

(仅限 CheckedListBox)返回一个值,指示项目是否被选中

GetItemCheckState

(仅限 CheckedListBox)返回一个值,指示项目的选中状态

SetItemChecked

(仅限 CheckedListBox)将指定的项目设置为
选中状态

SetItemCheckState

(仅限 CheckedListBox)设置项目的选中状态

ListBox 事件

通常,在使用列表框和 CheckedListBoxes 时,您会希望注意与用户所做选择相关的事件。

名称

描述

ItemCheck

(仅限 CheckedListBox)当某个列表项的选中状态更改时发生。

SelectedIndexChanged

当选定项的索引更改时发生。

尝试一下 – ListBox 示例

我们将创建一个包含 ListBox 和 CheckedListBox 的小示例。用户可以在 CheckedListBox 中选中项目,然后单击一个按钮,将选中的项目移动到普通 ListBox 中。我们按以下方式创建对话框

1.        在 Visual Studio.NET 中打开一个新项目,名为 Lists。向窗体中添加一个 ListBox、一个 CheckedListBox 和一个按钮,并如下图所示更改名称

2.        将按钮的 Text 属性更改为“移动”。

3.        将 CheckedListBox 的 CheckOnClick 属性更改为 true。

添加事件处理程序

现在我们准备添加一些代码。当用户单击“移动”按钮时,我们希望找到已选中的项目,并将它们复制到“选定列表框”中。

双击按钮并输入此代码

private void btnMove_Click(object sender, System.EventArgs e)
{
   // Check if there are any checked items in the CheckedListBox
   if (this.chkListPossibleValues.CheckedItems.Count > 0)
   {
      // Clear the ListBox we'll move the selections to
      this.lstSelected.Items.Clear();

      // Loop through the CheckedItems collection of the CheckedListBox
      // and add the items in the Selected ListBox
      foreach (string item in this.chkListPossibleValues.CheckedItems)
      {
         this.lstSelected.Items.Add(item.ToString());
      }

      // Clear all the checks in the CheckedListBox
      for (int i = 0; i < this.chkListPossibleValues.Items.Count; i++)
         this.chkListPossibleValues.SetItemChecked(i, false);
   }
}

我们首先检查 CheckedItems 集合的 Count 属性。如果集合中有任何项目被选中,此属性将大于零。然后我们清除 Selected 列表框中的所有项目,并遍历 CheckedItems 集合,将每个项目添加到 Selected 列表框中。最后,我们删除 CheckedListBox 中的所有选中标记。

现在我们只需要在 CheckedListBox 中移动一些东西。我们可以在设计模式下添加项目,方法是在属性面板中选择 Items 属性并在那里添加项目。相反,我们将通过代码添加项目。我们在窗体的构造函数中这样做

public Form1()
{
   //
   // Required for Windows Form Designer support
   //
   InitializeComponent();

   // Fill the CheckedListBox
   this.chkListPossibleValues.Items.Add("One");
   this.chkListPossibleValues.Items.Add("Two");
   this.chkListPossibleValues.Items.Add("Three");
   this.chkListPossibleValues.Items.Add("Four");
   this.chkListPossibleValues.Items.Add("Five");
   this.chkListPossibleValues.Items.Add("Six");
   this.chkListPossibleValues.Items.Add("Seven");
   this.chkListPossibleValues.Items.Add("Eight");
   this.chkListPossibleValues.Items.Add("Nine");
   this.chkListPossibleValues.Items.Add("Ten");
}

正如您通过属性面板输入值一样,您使用 Items 集合在运行时添加项目。

ListBox 示例到此结束,如果您现在运行它,您将得到类似这样的结果

ComboBox 控件

顾名思义,组合框结合了多个控件,具体来说是 TextBox、Button 和 ListBox 控件。与 ListBox 不同的是,组合框中包含的项目列表中永远不可能选择多个项目,并且可以选择在组合框的 TextBox 部分输入列表中的新条目。

通常,ComboBox 控件用于节省对话框上的空间,因为组合框中永久可见的部分只有文本框和按钮部分。当用户单击文本框右侧的箭头按钮时,会展开一个列表框,用户可以在其中进行选择。一旦他或她这样做,列表框就会消失,显示恢复正常。

现在我们将查看控件的属性和事件,然后创建一个使用 ComboBox 和两个 ListBox 控件的示例。

ComboBox 属性

由于 ComboBox 控件包含 TextBox 和 ListBox 控件的功能,因此该控件的许多属性也可以在其他两个控件上找到。因此,ComboBox 上有大量的属性和事件,我们在这里只介绍其中最常见的。有关完整列表,请参阅 MSDN 库。

名称

可用性

描述

DropDownStyle

读/写

组合框可以显示三种不同的样式

q         DropDown: 用户可以编辑控件的文本框部分,并且必须单击箭头按钮才能显示控件的列表部分。

q         Simple: 与 DropDown 相同,不同之处在于控件的列表部分始终可见,很像普通的 ListBox。

q         DropDownList: 用户无法编辑控件的文本框部分,并且必须单击箭头按钮才能显示控件的列表部分。

DroppedDown

读/写

指示控件的列表部分是否已下拉。如果将此属性设置为 true,则列表将展开。

Items

只读

此属性是一个集合,其中包含组合框中包含列表中的所有项目。

MaxLength

读/写

通过将此属性设置为非零值,您可以控制可以在控件的文本框部分输入的最大字符数。

SelectedIndex

读/写

指示列表中当前选定项目的索引。

SelectedItem

读/写

指示列表中当前选定的项目。
列表。

SelectedText

读/写

表示在控件的文本框部分中选定的文本。

SelectionStart

读/写

在控件的文本框部分中,此属性表示选定
的第一个字符的索引。

SelectionLength

读/写

控件文本框部分中选定文本的长度。

已排序

读/写

将此属性设置为 true,使控件将列表部分中的项目按字母顺序排序。

文本

读/写

如果将此属性设置为 null,则会删除控件列表部分中的任何选定内容。如果将其设置为列表中存在的值,则会选择该值。如果该值在列表中不存在,则文本只会显示在文本部分。

ComboBox 事件

有三个主要操作,您会希望在组合框上发生时得到通知。

  • 选定内容发生更改
  • 下拉状态发生更改
  • 文本发生更改

要处理这三个操作,您通常会订阅以下一个或多个事件

名称

描述

DropDown

当控件的列表部分下拉时发生。

SelectedIndexChanged

当控件列表部分中的选定内容发生更改时发生。
控件更改。

KeyDown

KeyPress

KeyUp

当控件的文本部分获得焦点时按下按键时,会发生这些事件。请参阅本章前面文本框部分中事件的描述。

TextChanged

当 Text 属性更改时发生。

试用一下 – ComboBox 示例

我们再次回顾 TextBox 示例中使用的示例。回想一下在 CheckBoxes 示例中,我们将 occupation TextBox 更改为 CheckBox。应该指出的是,除了程序员之外,还有可能拥有其他职业,因此我们应该再次更改对话框以适应更多职业。为此,我们将使用一个组合框,其中包含两种职业:顾问和程序员,并允许用户在 TextBox 中添加自己的职业,如果不同的话。

由于用户不应该每次使用我们的应用程序时都手动输入他或她的职业,因此我们会在对话框关闭时将 ComboBox 中的项目保存到文件中,并在启动时加载它。

让我们从对话框外观的更改开始

1.        删除名为 chkProgrammer 的 CheckBox。

2.        添加一个标签和一个 ComboBox,并如下图所示命名它们。

3.        将标签的 Text 属性更改为“职业”,并清除 ComboBox 上的 Text 属性。

4.        无需对窗体进行更多更改。但是,创建一个名为 Occupations.txt 的文本文件,其中包含以下两行

顾问

程序员

此示例可在本章的代码下载中找到,名为 ComboBox Visual Studio.NET 项目。

添加事件处理程序

现在我们准备更改代码。在开始编写新代码之前,我们将更改 btnOK_Click 事件处理程序以匹配窗体的更改。

private void btnOK_Click(object sender, System.EventArgs e)
{
   // No testing for invalid values are made, as that should
   // not be neccessary

   string output;

   // Concatenate the text values of the controls
   output = "Name: " + this.txtName.Text + "\r\n";
   output += "Address: " + this.txtAddress.Text + "\r\n";
   output += "Occupation: " + this.cboOccupation.Text + "\r\n";


   output += "Sex: " + (string)(this.rdoFemale.Checked ? "Female" : "Male") +
             "\r\n";
   output += "Age: " + this.txtAge.Text;

   // Insert the new text
   this.txtOutput.Text = output;
}

我们现在使用的是组合框而不是复选框,因此当在组合框中选择一个项目时,它会显示在控件的可编辑部分中。因此,我们感兴趣的值将始终在组合框的 Text 属性中。

现在我们准备开始编写新代码。我们将做的第一件事是创建一个方法,该方法加载文件中已存在的值并将其插入到组合框中。

private void LoadOccupations()
{
   try
   {
      // Create a StreamReader object. Change the path to where you put
      // the file
      System.IO.StreamReader sr =
                   new System.IO.StreamReader("../../Occupations.txt");

      string input;

      // Read as long as there are more lines
      do
      {
         input = sr.ReadLine();

         // Add only if the line read contains any characters
         if (input != "")
            this.cboOccupation.Items.Add(input);
      } while (sr.Peek() != -1);
                             // Peek returns -1 if at the end of the stream

      // Close the stream
      sr.Close();
   }
   catch (System.Exception)
   {
      MessageBox.Show("File not found");
   }
}

流阅读器在第 20 章中有所介绍,所以我们在这里不讨论此代码的复杂性。只需说,我们用它来打开文本文件 Occupations.txt 并逐行读取组合框的项目。我们使用 Items 集合上的 Add() 方法将文件中的每一行添加到组合框中。

由于用户应该能够在组合框中输入新项目,因此我们将添加一个检查,以查看是否按下了 Enter 键。如果 ComboBox 的 Text 属性中的文本在 Items 集合中不存在,我们将把新项目添加到其中。

private void cboOccupation_KeyDown(object sender,
                                   System.Windows.Forms.KeyEventArgs e)
{
   int index = 0;
   ComboBox cbo = (ComboBox)sender;

   // We only want to do something if the enter key was pressed
   if (e.KeyCode == Keys.Enter)
   {
      // FindStringExact searches for the string and is not
      // case-sensitive, which
      // is exactly what we need, as Programmer and programmer is the same.
      // If we find a match we'll move the selection in the ComboBox to
      // that item.
      index = cbo.FindStringExact(cbo.Text);
      if (index < 0) // FindStringExact return -1 if nothing was found.
         cbo.Items.Add(cbo.Text);
      else
         cbo.SelectedIndex = index;

      // Signal that we've handled the key down event
      e.Handled = true;
   }
}

ComboBox 对象的 FindStringExact() 方法搜索完全匹配的字符串,无论两个字符串的大小写如何。这非常适合我们,因为我们不希望将相同职业的各种情况添加到集合中。

如果在 Items 集合中没有找到与文本匹配的项目,我们就添加一个新项目。添加新项目会自动将当前选定的项目设置为新项目。如果找到匹配项,我们只需选择集合中现有的条目。

我们还需要订阅事件,我们将在窗体的构造函数中完成此操作

this.txtAge.TextChanged += new System.EventHandler(this.txtBox_TextChanged);
this.cboOccupation.KeyDown += new
           System.Windows.Forms.KeyEventHandler(this.cboOccupation_KeyDown);

当用户关闭对话框时,我们应该保存组合框中的项目。我们将在
另一种方法

private void SaveOccupation()
{
   try
   {
      System.IO.StreamWriter sw = new
                         System.IO.StreamWriter("../../Occupations.txt");
   foreach (string item in this.cboOccupation.Items)
      sw.WriteLine(item); // Write the item to the file
   sw.Flush();
   sw.Close();
   catch (System.Exception)
   {
      MessageBox.Show("File not found or moved");
   }
}

StreamWriter 类在第 20 章中有所介绍,因此不会讨论此代码的详细信息。但是,我们再次将文件 IO 代码包装在 try...catch 块中,以防万一有人在窗体打开时无意中删除或移动文本文件。我们遍历 Items 集合中的项目,并将每个项目写入文件。

最后,我们必须调用刚刚定义的 LoadOccupation() 和 SaveOccupation() 方法。我们分别在窗体构造函数和 Dispose() 方法中执行此操作。

public Form1()
{
   .
   .
   .
   this.cboOccupation.KeyDown += new
        System.Windows.Forms.KeyEventHandler(this.cboOccupation_KeyDown);

   // Fill the ComboBox
   LoadOccupations();
}

public override void Dispose()
{
   // Save the items in the ComboBox
   SaveOccupation();

   base.Dispose();
   if(components != null)
      components.Dispose();
} 

ComboBox 示例到此结束。运行示例时,您应该会看到类似
这样的内容。

ListView 控件

在 Windows 标准对话框中用于选择要打开的文件的列表是 ListView 控件。您可以使用 Visual Studio.NET 提供的 ListView 对标准列表视图对话框中的视图进行所有操作(大图标、详细信息视图等)。

列表视图通常用于呈现数据,其中允许用户对演示文稿的细节和样式进行一些控制。可以将控件中包含的数据显示为列和行,就像在网格中一样,也可以显示为单列或具有各种图标表示形式。最常用的列表视图类似于上面看到的,用于导航计算机上的文件夹。

ListView 控件是本章中我们将遇到的最复杂的控件,涵盖它的所有内容超出了本书的范围。我们将通过编写一个利用 ListView 控件许多最重要功能的示例,并详细描述可以使用的众多属性、事件和方法,为您提供一个坚实的基础。我们还将介绍 ImageList 控件,它用于存储 ListView 控件中使用的图像。

ListView 属性

名称

可用性

描述

激活

读/写

通过使用此属性,您可以控制用户如何激活列表视图中的项目。除非您有充分的理由这样做,否则您不应更改默认设置,因为您将更改用户为其整个系统设置的设置。

可能的值是

·Standard: 此设置是用户为其机器选择的设置。

·OneClick: 单击项目会激活它。

·TwoClick: 双击项目会激活它。

对齐

读/写

此属性允许您控制列表视图中项目的对齐方式。四个可能的值是

·          Default: 如果用户拖放一个项目,它将保留在他或她放置它的位置。

·          Left: 项目与 ListView 控件的左边缘对齐。

·          Top: 项目与 ListView 控件的顶部边缘对齐。

·          SnapToGrid: ListView 控件包含一个不可见的网格,项目将自动吸附到该网格。

AllowColumn
Reorder

读/写

如果将此属性设置为 true,则允许用户更改列表视图中列的顺序。如果这样做,您应该确保填充列表视图的例程即使在列顺序更改后也能正确插入项目。

AutoArrange

读/写

如果将此属性设置为 true,项目将根据 Alignment 属性自动排列。如果用户将项目拖到列表视图的中心,并且 Alignment 为 Left,则该项目将自动跳到列表视图的左侧。此属性仅在 View 属性为 LargeIcon 或 SmallIcon 时有意义。

CheckBoxes

读/写

如果将此属性设置为 true,则列表视图中的每个项目左侧都会显示一个 CheckBox。此属性仅在 View 属性为 Details 或 List 时有意义。

CheckedIndices

CheckedItems

只读

这两个属性分别提供对索引和项目集合的访问,其中包含列表中选中的项目。

Columns

只读

列表视图可以包含列。此属性允许您访问列集合,通过该集合可以添加或删除列。

FocusedItem

只读

此属性保存列表视图中具有焦点的项目。如果未选择任何内容,则为 null。

FullRowSelect

读/写

当此属性为 true 且单击项目时,项目所在的整个行将被突出显示。如果为 false,则仅项目本身将被突出显示。

GridLines

读/写

将此属性设置为 true 将使列表视图在行和列之间绘制网格线。此属性仅在 View 属性为 Details 时有意义。

HeaderStyle

读/写

您可以控制列标题的显示方式。有三种样式

·Clickable: 列标题像按钮一样工作。

·NonClickable: 列标题不响应鼠标单击。

·None: 不显示列标题。

HoverSelection

读/写

当此属性为 true 时,用户可以通过将鼠标指针悬停在项目上来选择列表视图中的项目。

Items

只读

列表视图中的项目集合。

LabelEdit

读/写

当此属性为 true 时,用户可以编辑详细信息视图中第一列的内容。

LabelWrap

读/写

如果此属性为 true,则标签将根据需要换行以显示所有文本。

LargeImageList

读/写

此属性保存 ImageList,其中包含大图像。当 View 属性为 LargeIcon 时,可以使用这些图像。

MultiSelect

读/写

将此属性设置为 true,以允许用户选择多个项目。

Scrollable

读/写

将此属性设置为 true 以显示滚动条。

SelectedIndices

SelectedItems

只读

这两个属性包含集合
分别包含选定的索引和项目。

SmallImageList

读/写

当 View 属性为 SmallIcon 时,此属性保存包含所用图像的 ImageList。

排序

读/写

您可以允许列表视图对其包含的项目进行排序。有三种可能的模式

·Ascending

·Descending

·None

StateImageList

读/写

ImageList 包含图像的蒙版,这些蒙版用作 LargeImageList 和 SmallImageList 图像上的叠加层,以表示
自定义状态。

TopItem

只读

返回列表视图顶部的项目。

视图

读/写

列表视图可以显示其项目有四种
不同模式

·LargeIcon: 所有项目都显示带有大图标(32x32)和标签。

·SmallIcon: 所有项目都显示带有小图标(16x16)和标签。

·List: 只显示一列。该列可以包含一个图标和标签。

·Details: 可以显示任意数量的列。只有第一列可以包含图标。

ListView 方法

对于像列表视图这样复杂的控件,特定于它的方法出奇地少。它们都在下表中进行了描述。

名称

描述

BeginUpdate

通过调用此方法,您告诉列表视图停止绘制更新,直到调用 EndUpdate。当您一次插入许多项目时,这很有用,因为它阻止了视图闪烁并显著提高了速度。

Clear

完全清除列表视图。所有项目和列都将被删除。

EndUpdate

在调用 BeginUpdate 后调用此方法。当您调用此方法时,列表视图将绘制其所有项目。

EnsureVisible

当您调用此方法时,列表视图将自行滚动,以使您指定的索引项目可见。

GetItemAt

返回列表视图中位置 x,y 处的项目。

ListView 事件

这些是您可能需要处理的 ListView 控件事件

名称

描述

AfterLabelEdit

此事件在标签编辑后发生。

BeforeLabelEdit

此事件在用户开始编辑标签之前发生。

ColumnClick

此事件在单击列时发生。

ItemActivate

当项目被激活时发生。

ListViewItem

列表视图中的项目始终是 ListViewItem 类的实例。ListViewItem 包含文本和要显示的图标索引等信息。ListViewItem 有一个名为 SubItems 的集合,其中包含另一个类 ListViewSubItem 的实例。如果 ListView 控件处于详细信息模式,则会显示这些子项。每个子项都表示列表视图中的一列。子项和主项的主要区别在于子项不能显示图标。

您通过 Items 集合将 ListViewItems 添加到 ListView,通过 ListViewItem 上的 SubItems 集合将 ListViewSubItems 添加到 ListViewItem。

ColumnHeader

要使列表视图显示列标题,您需要将 ColumnHeader 类实例添加到 ListView 的 Columns 集合中。ColumnHeaders 为当 ListView 处于详细信息模式时可以显示的列提供标题。

ImageList 控件

ImageList 控件提供了一个集合,可用于存储窗体上其他控件中使用的图像。您可以在图像列表中存储任何大小的图像,但在每个控件中,每个图像必须具有相同的大小。对于 ListView 而言,这意味着您需要两个 ImageList 控件才能同时显示大图像和小图像。

ImageList 是我们本章中遇到的第一个在运行时不显示自身的控件。当您将其拖到正在开发的窗体上时,它不会放在窗体本身上,而是放在窗体下方的托盘中,其中包含所有此类组件。此功能非常有用,可以防止不属于用户界面的控件占用窗体设计器。控件的操作方式与任何其他控件完全相同,只是您无法移动它。

您可以在设计时和运行时向 ImageList 添加图像。如果您在设计时知道需要显示哪些图像,可以通过单击 Images 属性右侧的按钮来添加图像。这将弹出一个对话框,您可以在其中浏览要插入的图像。如果您选择在运行时添加图像,则通过 Images 集合添加它们。

有关如何使用此控件的示例,请参阅下面的 ListView 示例。

尝试一下 – ListView 示例

学习使用 ListView 控件及其关联图像列表的最佳方法是通过一个示例。我们将创建一个包含 ListView 和两个 ImageList 的对话框。ListView 将显示硬盘驱动器上的文件和文件夹。为了简单起见,我们不会从文件和文件夹中提取正确的图标,而是对文件夹使用标准文件夹图标,对文件使用信息图标。

通过双击文件夹,您可以浏览文件夹树,并提供一个后退按钮来向上移动树。四个单选按钮用于在运行时更改列表视图的模式。如果双击文件,我们将尝试执行它。

一如既往,我们将从创建用户界面开始

1.        创建一个新的 Visual Studio.NET 项目,名为 ListView。向窗体中添加一个 ListView、一个按钮、一个标签和一个分组框。然后向分组框中添加四个单选按钮,以获得如下图所示的窗体。

2.        按照上图所示命名控件。ListView 不会像上图中那样显示其名称;我已向其中添加了一个项目以提供名称。您不应该这样做。

3.        将单选按钮和按钮的 Text 属性更改为与名称相同,但前三个字母除外。

4.        清除标签的 Text 属性。

5.        通过双击工具箱中控件的图标(您需要向下滚动才能找到它)向窗体添加两个 ImageList。将它们重命名为 ilSmall 和 ilLarge。

6.        将名为 ilLarge 的 ImageList 的 Size 属性更改为 32, 32。

7.        单击 ilLage 图像列表的 Images 属性右侧的按钮,以弹出对话框,您可以在其中浏览要插入的图像。

8.        单击“添加”并浏览到包含图像的 Visual Studio.NET 下的文件夹。文件是

<驱动器>:\Program Files\Visual Studio.NET\Common7\Graphics\Icons\Win95\clsdfold.ico

<驱动器>:\Program Files\Visual Studio.NET\Common7\Graphics\Icons\Computer\msgbox04.ico

9.        确保文件夹图标位于列表顶部。

10.   重复步骤 7 和 8,使用另一个 ImageList,ilSmall。

11.   将单选按钮 rdoDetails 的 Checked 属性设置为 true。

12.   在列表视图上设置以下属性

MultiSelect = true

LargeImageList = ilLarge

SmallImageList = ilSmall

View = Details

13.   按照上图所示更改窗体和框架的 Text 属性。

添加事件处理程序

我们的用户界面到此结束,我们可以继续编写代码。首先,我们需要一个字段来保存我们浏览过的文件夹,以便在单击后退按钮时能够返回到它们。我们将存储文件夹的绝对路径,因此我们选择 StringCollection 来完成这项工作。

public class Form1 : System.Windows.Forms.Form
{
   // Member field to hold previous folders
   private System.Collections.Specialized.StringCollection folderCol;

我们没有在窗体设计器中创建任何列标题,所以我们现在必须这样做。我们在一个名为 CreateHeadersAndFillListView() 的方法中创建它们。

private void CreateHeadersAndFillListView()
{
   ColumnHeader colHead;

   // First header
   colHead = new ColumnHeader();
   colHead.Text = "Filename";
   this.lwFilesAndFolders.Columns.Add(colHead); // Insert the header

   // Second header
   colHead = new ColumnHeader();
   colHead.Text = "Size";
   this.lwFilesAndFolders.Columns.Add(colHead); // Insert the header

   // Third header
   colHead = new ColumnHeader();
   colHead.Text = "Last accessed";
   this.lwFilesAndFolders.Columns.Add(colHead); // Insert the header
}

我们首先声明一个名为 colHead 的变量,我们将使用它来创建三个列标题。对于三个标题中的每一个,我们都将变量实例化,并将 Text 分配给它,然后将其添加到 ListView 的 Columns 集合中。

窗体第一次显示时的最终初始化是将列表视图填充硬盘中的文件和文件夹。这在另一个方法中完成。

private void PaintListView(string root)
{
   try
   {
      // Two local variables that is used to create the items to insert
      ListViewItem lvi;
      ListViewItem.ListViewSubItem lvsi;

      // If there's no root folder, we can't insert anything
      if (root.CompareTo("") == 0)
         return;

      // Get information about the root folder.
      System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(root);

      // Retrieve the files and folders from the root folder.
      DirectoryInfo[] dirs = dir.GetDirectories(); // Folders
      FileInfo[] files = dir.GetFiles();           // Files

      // Clear the ListView. Note that we call the Clear method on the
      // Items collection rather than on the ListView itself.
      // The Clear method of the ListView remove everything, including column
      // headers, and we only want to remove the items from the view.
      this.lwFilesAndFolders.Items.Clear();

      // Set the label with the current path
      this.lblCurrentPath.Text = root;

      // Lock the ListView for updates
      this.lwFilesAndFolders.BeginUpdate();

      // Loop through all folders in the root folder and insert them
      foreach (System.IO.DirectoryInfo di in dirs)
      {
         // Create the main ListViewItem
         lvi = new ListViewItem();
         lvi.Text = di.Name; // Folder name
         lvi.ImageIndex = 0; // The folder icon has index 0
         lvi.Tag = di.FullName; // Set the tag to the qualified path of the
                             // folder

         // Create the two ListViewSubItems.
         lvsi = new ListViewItem.ListViewSubItem();
         lvsi.Text = ""; // Size - a folder has no size and so this column
                         // is empty
         lvi.SubItems.Add(lvsi); // Add the sub item to the ListViewItem

         lvsi = new ListViewItem.ListViewSubItem();
         lvsi.Text = di.LastAccessTime.ToString(); // Last accessed column
         lvi.SubItems.Add(lvsi); // Add the sub item to the ListViewItem

         // Add the ListViewItem to the Items collection of the ListView
         this.lwFilesAndFolders.Items.Add(lvi);
      }

      // Loop through all the files in the root folder
      foreach (System.IO.FileInfo fi in files)
      {
         // Create the main ListViewItem
         lvi = new ListViewItem();
         lvi.Text = fi.Name; // Filename
         lvi.ImageIndex = 1; // The icon we use to represent a folder has
         // index 1
         lvi.Tag = fi.FullName; // Set the tag to the qualified path of the
         // file

         // Create the two sub items
         lvsi = new ListViewItem.ListViewSubItem();
         lvsi.Text = fi.Length.ToString(); // Length of the file
         lvi.SubItems.Add(lvsi); // Add to the SubItems collection

         lvsi = new ListViewItem.ListViewSubItem();
         lvsi.Text = fi.LastAccessTime.ToString(); // Last Accessed Column
         lvi.SubItems.Add(lvsi); // Add to the SubItems collection

         // Add the item to the Items collection of the ListView
         this.lwFilesAndFolders.Items.Add(lvi);
      }

      // Unlock the ListView. The items that have been inserted will now
      // be displayed
      this.lwFilesAndFolders.EndUpdate();
   }
   Catch (System.Exception err)
   {
      MessageBox.Show("Error: " + err.Message);
   }
}

在两个 foreach 块中的第一个之前,我们调用 ListView 控件上的 BeginUpdate()。请记住,ListView 上的 BeginUpdate() 方法会向 ListView 控件发出信号,使其停止更新其可见区域,直到调用 EndUpdate()。如果我们不调用此方法,填充列表视图会更慢,并且列表在添加项目时可能会闪烁。在第二个 foreach 块之后,我们调用 EndUpdate(),这将使 ListView 控件绘制我们已填充的项目。

这两个 foreach 块包含我们感兴趣的代码。我们首先创建一个新的 ListViewItem 实例,然后将 Text 属性设置为要插入的文件或文件夹的名称。ListViewItem 的 ImageIndex 指的是 ImageList 中项目的索引。因此,图标在两个 ImageList 中具有相同的索引非常重要。我们使用 Tag 属性保存文件夹和文件的完全限定路径,以便在用户双击项目时使用。

然后我们创建两个子项。它们只是被分配要显示的文本,然后添加到 ListViewItem 的 SubItems 集合中。

最后,ListViewItem 被添加到 ListView 的 Items 集合中。如果视图模式不是详细信息模式,ListView 会足够智能地忽略子项,因此无论当前视图模式如何,我们都会添加子项。

列表视图显示根文件夹所需的所有工作,就是调用窗体构造函数中的两个函数。同时,我们使用根文件夹实例化 folderColStringCollection。

InitializeComponent();

// Init ListView and folder collection
folderCol = new System.Collections.Specialized.StringCollection();
CreateHeadersAndFillListView();
PaintListView(@"C:\");
folderCol.Add(@"C:\");

为了允许用户双击 ListView 中的项目以浏览文件夹,我们需要订阅 ItemActivate 事件。我们将订阅添加到构造函数中。

this.lwFilesAndFolders.ItemActivate += new
                   System.EventHandler(this.lwFilesAndFolders_ItemActivate);

相应的事件处理程序如下所示

private void lwFilesAndFolders_ItemActivate(object sender, System.EventArgs e)
{
   // Cast the sender to a ListView and get the tag of the first selected
   // item.
   System.Windows.Forms.ListView lw = (System.Windows.Forms.ListView)sender;
   string filename = lw.SelectedItems[0].Tag.ToString();

   if (lw.SelectedItems[0].ImageIndex != 0)
   {
      try
      {
         // Attempt to run the file
         System.Diagnostics.Process.Start(filename);
      }
      catch
      {
         // If the attempt fails we simply exit the method
         return;
      }
   }
   else
   {
      // Insert the items
      PaintListView(filename);
      folderCol.Add(filename);
   }
}

选定项目的标签包含双击的文件或文件夹的完全限定路径。我们知道索引为 0 的图像是文件夹,因此我们可以通过查看该索引来确定项目是文件还是文件夹。如果它是文件,我们尝试加载该文件。

如果它是一个文件夹,我们使用新文件夹调用 PaintListView,然后将新文件夹添加到 folderCol 集合中。

在进入单选按钮之前,我们将通过向“后退”按钮添加单击事件来完成浏览功能。双击该按钮并使用此代码填充事件处理程序。

private void btnBack_Click(object sender, System.EventArgs e)
{
   if (folderCol.Count > 1)
   {
      PaintListView(folderCol[folderCol.Count-2].ToString());
      folderCol.RemoveAt(folderCol.Count-1);
   }
   else
   {
      PaintListView(folderCol[0].ToString());
   }
}

如果 folderCol 集合中包含多个项目,则我们不在浏览器的根目录,我们会使用上一个文件夹的路径调用 PaintListView。folderCol 集合中的最后一个项目是当前文件夹,这就是为什么我们需要获取倒数第二个项目的原因。然后我们删除集合中的最后一个项目,并将新的最后一个项目设为当前文件夹。如果集合中只有一个项目,我们只需使用该项目调用 PaintListView。

剩下要做的就是能够更改列表视图的视图类型。双击每个单选按钮并添加以下代码

private void rdoLarge_CheckedChanged(object sender, System.EventArgs e)
{
   RadioButton rdb = (RadioButton)sender;
   if (rdb.Checked)
      this.lwFilesAndFolders.View = View.LargeIcon;
}

private void rdoList_CheckedChanged(object sender, System.EventArgs e)
{
   RadioButton rdb = (RadioButton)sender;
   if (rdb.Checed)
      this.lwFilesAndFolders.View = View.List;
}

private void rdoSmall_CheckedChanged(object sender, System.EventArgs e)
{
   RadioButton rdb = (RadioButton)sender;
   if (rdb.Checked)
      this.lwFilesAndFolders.View = View.SmallIcon;
}

private void rdoDetails_CheckedChanged(object sender, System.EventArgs e)
{
   RadioButton rdb = (RadioButton)sender;
   if (rdb.Checked)
      this.lwFilesAndFolders.View = View.Details;
}

我们检查单选按钮是否变为 Checked,如果是,则相应地设置 ListView 的 View 属性。

ListView 示例到此结束。运行它时,您应该会看到类似这样的结果

StatusBar 控件

状态栏通常用于提供有关选定项目或对话框上当前正在执行的操作的信息。通常,StatusBar 位于屏幕底部,就像在 MS Office 应用程序中一样,但它可以位于您喜欢的任何位置。Visual Studio.NET 提供状态栏,可用于简单地显示文本,或者您可以向其中添加面板并显示文本,或者创建自己的例程来绘制面板的内容。

上图显示了 MS Word 中的状态栏。状态栏中的面板可以识别为那些凹陷的部分。

StatusBar 属性

如上所述,您可以简单地将 StatusBar 控件的 Text 属性赋值以向用户显示简单文本,但也可以创建面板并将其用于相同的效果。

名称

可用性

描述

BackgroundImage

读/写

可以将图像分配给状态栏,该图像将在背景中绘制。

面板

只读

这是状态栏中的面板集合。使用此集合可以添加和删除面板。

ShowPanels

读/写

如果要显示面板,则此属性必须设置为 true。

文本

读/写

当不使用面板时,此属性保存状态栏中显示的文本。

StatusBar 事件

状态栏没有很多新事件,但是如果您手动绘制面板,DrawItem 事件至关重要。

名称

描述

DrawItem

当设置为 OwnerDraw 样式的面板需要重新绘制时发生。如果您想自行绘制面板内容,则必须订阅此事件。

PanelClick

单击面板时发生。

StatusBarPanel 类

状态栏中的每个面板都是 StatusBarPanel 类的实例。此类包含有关 Panels 集合中单个面板的所有信息。可以设置的信息范围从简单的文本和文本对齐方式到要显示的图标以及面板的样式。

如果您想自己绘制面板,则必须将面板的 Style 属性设置为 OwnerDraw,并处理 StatusBar 的 DrawItem 事件。

StatusBar 示例

我们将更改之前创建的 ListView 示例,以演示 StatusBar 控件的使用。我们将删除用于显示当前文件夹的标签,并将该信息移动到状态栏上的一个面板中。我们还将显示第二个面板,该面板将显示列表视图的当前视图模式。

1.        删除标签 lblCurrentFolder。

2.        双击工具箱中的 StatusBar 控件以将其添加到窗体(它再次位于列表底部附近)。新控件将自动停靠在窗体的底部边缘。

3.        将 StatusBar 的名称更改为 sbInfo 并清除 Text 属性。

4.        找到 Panels 属性并双击其右侧的按钮以弹出添加面板的对话框。

5.        单击“添加”以向集合添加一个面板。将 AutoSize 属性设置为 Spring。这意味着该面板将与状态栏中的其他面板共享空间。

6.        再次单击“添加”,并将 AutoSize 属性更改为 Contents。这意味着面板将根据其包含的文本大小调整自身大小。将 MinSize 属性设置为 0。

7.        单击“确定”关闭对话框。

8.        将 StatusBar 上的 ShowPanels 属性设置为 true。

此示例可在本章的代码下载中找到,名为 StatusBar Visual Studio.NET 项目。

添加事件处理程序

用户界面部分到此结束,我们将继续编写代码。我们将首先在 PaintListView 方法中设置当前路径。删除设置标签文本的行,并将其替换为以下内容。

this.sbInfo.Panels[0].Text = root;

第一个面板的索引为 0,我们只需设置其 Text 属性,就像设置标签的 Text 属性一样。最后,我们更改四个单选按钮的 CheckedChanged 事件,以设置第二个面板的文本。

private void rdoLarge_CheckedChanged(object sender, System.EventArgs e)
{
   RadioButton rdb = (RadioButton)sender;
   if (rdb.Checked)
   {
      this.lwFilesAndFolders.View = View.LargeIcon;
      this.sbInfo.Panels[1].Text = "Large Icon";
   }
}

private void rdoList_CheckedChanged(object sender, System.EventArgs e)
{
   RadioButton rdb = (RadioButton)sender;
   if (rdb.Checked)
   {
      this.lwFilesAndFolders.View = View.List;
      this.sbInfo.Panels[1].Text = "List";
   }
}

private void rdoSmall_CheckedChanged(object sender, System.EventArgs e)
{
   RadioButton rdb = (RadioButton)sender;
   if (rdb.Checked)
   {
      this.lwFilesAndFolders.View = View.SmallIcon;
      this.sbInfo.Panels[1].Text = "Small Icon";
   }
}

private void rdoDetails_CheckedChanged(object sender, System.EventArgs e)
{
   RadioButton rdb = (RadioButton)sender;
   if (rdb.Checked)
   {
      this.lwFilesAndFolders.View = View.Details;
      this.sbInfo.Panels[1].Text = "Details";
   }
}

面板文本的设置方式与上面的 PaintListView 中完全相同。

StatusBar 示例到此结束。如果您现在运行它,您应该会看到类似这样的内容

TabControl 控件

TabControl 提供了一种简单的方法,将对话框组织成逻辑部分,可以通过位于控件顶部的选项卡进行访问。TabControl 包含 TabPages,其工作方式与 GroupBox 控件基本相似,尽管它更复杂一些。

上图显示了 MS Word 2000 中的“选项”对话框的典型配置。请注意对话框顶部的两行选项卡。单击每个选项卡将显示对话框其余部分中不同控件的选择。这是一个很好的示例,说明如何使用选项卡控件将相关信息分组在一起,使户更容易找到他/她正在寻找的信息。

使用选项卡控件非常简单。您只需将要显示的选项卡数量添加到控件的 TabPages 集合中,然后将要显示的控件拖到相应的页面上即可。

在运行时,您可以通过控件的属性导航选项卡。

TabControl 属性

TabControl 的属性主要用于控制 TabPages 容器(特别是显示的选项卡)的外观。

名称

可用性

描述

对齐

读/写

控件的选项卡显示在选项卡控件的哪个位置。默认位于顶部。

外观

读/写

控制选项卡的显示方式。选项卡可以显示为普通按钮或平面样式。

HotTrack

读/写

如果此属性设置为 true,则当鼠标指针经过控件上的选项卡时,选项卡的外观会发生变化。

Multiline

读/写

如果此属性设置为 true,则可以有多个选项卡行。

RowCount

只读

返回当前显示的选项卡行数。

SelectedIndex

读/写

返回或设置选定选项卡的索引。

TabCount

只读

返回选项卡的总数。

TabPages

只读

这是控件中 TabPages 的集合。使用此集合可以添加和删除 TabPages。

使用 TabControl

TabControl 的工作方式与我们目前见过的所有其他控件略有不同。当您将控件拖到窗体上时,您会看到一个灰色矩形,它与上面屏幕截图中显示的控件不是很像。您还会看到,在“属性”面板下方,有两个看起来像链接的按钮,上面带有“添加选项卡”和“删除选项卡”的标题。单击“添加选项卡”将在控件上插入一个新选项卡页面,然后控件将开始变得可识别。显然,您可以使用“删除选项卡”链接按钮删除选项卡。

上述过程旨在让您快速上手使用该控件。另一方面,如果您想更改选项卡的行为或样式,则应使用 TabPages 对话框 – 通过在属性面板中选择 TabPages 时单击按钮来访问。

TabPages 属性也是用于访问选项卡控件上各个页面的集合。让我们创建一个示例来演示该控件的基本知识。该示例演示了如何控制位于选项卡控件上不同页面的控件。

1.        创建一个新的 C# Windows 应用程序项目并将其命名为 TabControl。

2.        将 TabControl 控件从工具箱拖到窗体上。

3.        单击“添加选项卡”以向控件添加一个选项卡。

4.        找到 TabPages 属性并在选择它后单击其右侧的按钮。

5.        单击“添加”以向控件添加另一个选项卡页面。

6.        将选项卡页面的 Text 属性分别更改为 Tab One 和 Tab Two。

7.        您可以通过单击控件顶部的选项卡来选择要处理的选项卡页面。选择文本为 TabOne 的选项卡。将一个按钮拖到控件上。确保将按钮放置在 TabControl 的框架内。如果将其放置在框架之外,则按钮将放置在窗体上而不是控件上。

8.        将按钮的名称更改为 btnShowMessage,将按钮的 Text 属性更改为 Show Message。

9.        单击 Text 属性为 TabTwo 的选项卡。将一个 TextBox 控件拖到 TabControl 表面。将此控件命名为 txtMessage 并清除 Text 属性。

10.   在对话框中单击“确定”返回窗体。这两个选项卡应该如下面这两个屏幕截图所示

 

添加事件处理程序

现在我们准备访问控件。如果您运行代码,您将看到选项卡页面正确显示。为了演示选项卡控件的使用,我们剩下要做的就是添加一些代码,以便当用户单击一个选项卡上的“显示消息”按钮时,在另一个选项卡中输入的文本将显示在消息框中。首先,我们通过双击第一个选项卡上的按钮并添加以下代码来添加 Click 事件的处理程序。

private void btnShowMessage_Click(object sender, System.EventArgs e)
{
   // Access the TextBox

   MessageBox.Show(this.txtMessage.Text);
}

您访问选项卡上的控件的方式与访问窗体上的任何其他控件相同。我们获取 TextBox 的 Text 属性并在消息框中显示它。

在本章前面,我们看到窗体上一次只能选择一个单选按钮(除非将它们放在分组框中)。TabPages 的工作方式与分组框完全相同,因此,可以在不同选项卡上拥有多个单选按钮组,而无需分组框。

您必须了解的最后一个与选项卡控件一起工作的知识是如何确定当前显示的是哪个选项卡。您可以使用两个属性来达到此目的:SelectedTab 和 SelectedIndex。顾名思义,SelectedTab 将返回 TabPage 对象或在未选择任何选项卡时返回 null,而 SelectedIndex 将返回选项卡的索引或在未选择任何选项卡时返回 -1。

摘要

在本章中,我们回顾了一些在创建 Windows 应用程序时最常用的控件,并了解了它们如何用于创建简单而功能强大的用户界面。我们讨论了这些控件的属性和事件,并给出了它们的使用示例。

本章讨论的控件有

  • Label
  • Button
  • RadioButton
  • CheckBox
  • ComboBox
  • ListBox
  • ListView
  • GroupBox
  • RichTextBox
  • StatusBar
  • ImageList
  • TabControl

在第 14 章中,我们将研究更复杂的控件,例如菜单和工具栏,并将使用它们来开发多文档界面 (MDI) Windows 应用程序。我们还将演示如何创建用户控件,它结合了本章中介绍的简单控件的功能。

版权和署名声明

本章摘自 Marco Bellinaso、Ollie Cornes、David Espinosa、Zach Greenvoss、Jacob Hammer Pedersen、Christian Nagel、Jon D Reid、Matthew Reynolds、Morgan Skinner、Karli Watson、Eric White 于 2001 年 9 月由 Wrox Press Limited 出版 的《Beginning C#》;ISBN 1861004982;版权所有 © Wrox Press Limited 2001。

未经出版商事先书面许可,不得以任何形式或任何方式(电子、静电、机械、影印、录音或其他方式)复制、存储在检索系统中或传输本章的任何部分,但评论文章中引用的简短引文除外。

© . All rights reserved.