5. 使用 VB.NET 和 VS.NET 开发图形界面
本文旨在演示如何使用 VB.NET 构建图形用户界面。 首先,我们将探讨 .NET 平台中用于构建图形用户界面的核心类。起初,我们将不使用任何自动生成工具。随后,我们将使用 Visual Studio.NET(VS.NET)——这是一款微软开发的工具,旨在简化使用 .NET 语言的应用程序开发,特别是图形用户界面的创建。本文使用的 VS.NET 版本为英文版。
5.1. 图形用户界面的基础
5.1.1. 一个简单的窗口
请看以下代码:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Drawing
Imports System.Windows.Forms
' the form class
Public Class Form1
Inherits Form
' the manufacturer
Public Sub New()
' window title
Me.Text = "Mon premier formulaire"
' window dimensions
Me.Size = New System.Drawing.Size(300, 100)
End Sub
' test function
Public Shared Sub Main(ByVal args() As String)
' the form is displayed
Application.Run(New Form1)
End Sub
End Class
上述代码先编译,然后执行
执行后显示以下窗口:

图形用户界面通常继承自基类 System.Windows.Forms.Form:
基类 Form 定义了一个基本窗口,包含关闭、最大化/最小化按钮、可调整大小等功能,并处理这些图形对象的事件。在此,我们通过设置其标题、宽度(300)和高度(100)来对基类进行特化。这些设置在构造函数中完成:
Public Sub New()
' window title
Me.Text = "Mon premier formulaire"
' window dimensions
Me.Size = New System.Drawing.Size(300, 100)
End Sub
窗口标题由 Text 属性设置,尺寸由 Size 属性设置。Size 定义在 System.Drawing 命名空间中,是一个结构体。Main 过程通过以下方式启动图形应用程序:
Application.Run(New Form1)
系统会创建并显示一个 Form1 类型的窗体,随后应用程序监听该窗体上发生的事件(如点击、鼠标移动等),并执行由窗体处理的事件。在此,我们的窗体除了由基类 Form 处理的事件(关闭按钮点击、最大化/最小化按钮点击、窗口调整大小、窗口移动等)之外,不处理任何其他事件。
5.1.2. 带按钮的窗体
现在,让我们在窗口中添加一个按钮:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Drawing
Imports System.Windows.Forms
' the form class
Public Class Form1
Inherits Form
' attributes
Private cmdTest As Button
' the manufacturer
Public Sub New()
' the title
Me.Text = "Mon premier formulaire"
' dimensions
Me.Size = New System.Drawing.Size(300, 100)
' a button
' creation
Me.cmdTest = New Button
' position
cmdTest.Location = New System.Drawing.Point(110, 20)
' size
cmdTest.Size = New System.Drawing.Size(80, 30)
' wording
cmdTest.Text = "Test"
' event manager
AddHandler cmdTest.Click, AddressOf cmdTest_Click
' add button to form
Me.Controls.Add(cmdTest)
End Sub
' event manager
Private Sub cmdTest_Click(ByVal sender As Object, ByVal evt As EventArgs)
' there was a click on the button - we say it
MessageBox.Show("Clic sur bouton", "Clic sur bouton", MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub
' test function
Public Shared Sub Main(ByVal args() As String)
' the form is displayed
Application.Run(New Form1)
End Sub
End Class
我们在窗体上添加了一个按钮:
' un bouton
' création
Me.cmdTest = New Button
' position
cmdTest.Location = New System.Drawing.Point(110, 20)
' taille
cmdTest.Size = New System.Drawing.Size(80, 30)
' libellé
cmdTest.Text = "Test"
' gestionnaire d'évt
AddHandler cmdTest.Click, AddressOf cmdTest_Click
' ajout bouton au formulaire
Me.Controls.Add(cmdTest)
Location 属性使用 Point 结构设置按钮左上角的坐标 (110,20)。按钮的宽度和高度使用 Size 结构设置为 (80,30)。按钮的 Text 属性设置按钮的标签。Button 类定义了 Click 事件,如下所示:
其中 EventHandler 是一个具有以下签名的“委托”函数:
这意味着按钮上 [Click] 事件的处理程序必须具有 [EventHandler] 委托的签名。在此,当点击 cmdTest 按钮时,将调用 cmdTest_Click 方法。该方法的定义如下,符合前面的 EventHandler 模型:
' event manager
Private Sub cmdTest_Click(ByVal sender As Object, ByVal evt As EventArgs)
' there was a click on the button - we say it
MessageBox.Show("Clic sur bouton", "Clic sur bouton", MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub
我们只是显示一条消息:

该类经过编译并执行:
dos>vbc /r:system.dll /r:system.drawing.dll /r:system.windows.forms.dll frm2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
dos>frm2
MessageBox 类用于在窗口中显示消息。在此,我们使用了构造函数
Overloads Public Shared Function Show(ByVal owner As IWin32Window,ByVal text As String,ByVal caption As String,ByVal buttons As MessageBoxButtons,ByVal icon As MessageBoxIcon) As DialogResult
text | 要显示的消息 |
标题 | 窗口标题 |
按钮 | 窗口中的按钮 |
图标 | 窗口中的图标 |
“buttons”参数可以取以下常量的值:
常量 | buttons |
![]() | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() |
icon 参数可以取以下常量的值:
![]() | 与上文相同 停止 | ||
与“警告”相同 | ![]() | ||
与Asterisk相同 | ![]() | ||
![]() | 与“手”相同 | ||
![]() |
Show 方法是一个静态方法,它返回类型为 System.Windows.Forms.DialogResult 的结果,该类型是一个枚举:
public enum System.Windows.Forms.DialogResult
{
Abort = 0x00000003,
Cancel = 0x00000002,
Ignore = 0x00000005,
No = 0x00000007,
None = 0x00000000,
OK = 0x00000001,
Retry = 0x00000004,
Yes = 0x00000006,
}
要确定用户点击了哪个按钮来关闭 MessageBox 窗口,我们编写:
dim res as DialogResult=MessageBox.Show(..)
if res=DialogResult.Yes then
' he pressed the yes button
...
end if
5.2. 使用 Visual Studio.NET 构建图形用户界面
我们将重新审视之前看到的一些示例,这次使用 Visual Studio.NET 来构建它们。
5.2.1. 初始项目创建
- 启动 VS.NET,然后选择“文件”>“新建”>“项目”

- 指定项目的属性
![]() |
- 选择要构建的项目类型;此处为 VB.NET 项目 (1)
- 选择要构建的应用程序类型;此处为 Windows 应用程序 (2)
- 指定要放置项目子文件夹的文件夹(3)
- 输入项目名称(4)。这也将是存放项目文件的文件夹名称
- 该文件夹的名称将显示在 (5) 中
- 随后将在 i4 文件夹下创建若干文件夹和文件:
project1文件夹的子文件夹 ![]() |
在这些文件中,只有一个与我们相关:form1.cs 文件,它是 VS.NET 创建的窗体对应的源文件。我们稍后会再回到这个文件。
5.2.2. VS.NET 的界面窗口
VS.NET 界面现在显示了我们 i4 项目的某些元素:
我们有一个图形用户界面设计窗口:
![]() |
通过从工具栏(工具箱 2)中拖动控件并将其拖放到窗口区域(1)上,我们可以构建一个图形用户界面。如果将鼠标悬停在“工具箱”上,它会展开并显示多个控件:

目前,我们暂不使用其中任何一个。在 VS.NET 界面中,我们可以找到“解决方案资源管理器”窗口:

起初,我们不会经常使用这个窗口。它显示了构成该项目的所有文件。我们只关注其中一个:程序的源文件,在本例中是 Form1.vb。右键单击 Form1.vb 会弹出一个菜单,允许您访问 GUI 的源代码(“查看代码”)或 GUI 本身(“窗体设计器”):

您也可以直接从“解决方案资源管理器”窗口访问这两者:
![]() | ![]() |
打开的窗口会在主设计窗口中“堆叠”显示:

在此,Form1.vb[Design] 指设计窗口,Form1.vb 指代码窗口。只需单击其中一个选项卡即可在两个窗口之间切换。VS.NET 屏幕上显示的另一个重要窗口是“属性”窗口:

该窗口中显示的属性属于图形设计窗口中当前选中的控件。您可以通过“视图”菜单访问不同的项目窗口:

其中包含上述主要窗口及其键盘快捷键。
5.2.3. 运行项目
尽管我们尚未编写任何代码,但已有一个可执行的项目。按 F5 或选择“调试/开始”来运行它。我们将看到以下窗口:

该窗口可以最大化、最小化、调整大小以及关闭。
5.2.4. VS.NET 生成的代码
让我们查看应用程序的代码(“视图”/“代码”):
Public Class Form1
Inherits System.Windows.Forms.Form
#Region " Code généré par le Concepteur Windows Form "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after InitializeComponent() call
End Sub
'The substituted method Disposes of the form to clean up the list of components.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'REMARQUE: the following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
components = New System.ComponentModel.Container()
Me.Text = "Form2"
End Sub
#End Region
End Class
图形用户界面继承自基类 System.Windows.Forms.Form:
Form 基类定义了一个基本窗口,该窗口具有关闭和最大化/最小化按钮、可调整大小等功能,并处理这些图形对象上的事件。窗体的构造函数使用 InitializeComponent 方法,在该方法中创建并初始化窗体的控件。
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after InitializeComponent() call
End Sub
构造函数中需要执行的其他操作均可在调用 InitializeComponent 之后进行。InitializeComponent 方法
Private Sub InitializeComponent()
'
'Form1
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(292, 53)
Me.Name = "Form1"
Me.Text = "Form1"
End Sub
设置了“Form1”窗口的标题、宽度(292)和高度(53)。窗口标题由 Text 属性设置,尺寸由 Size 属性设置。Size 定义在 System.Drawing 命名空间中,是一个结构体。要运行此应用程序,我们需要定义项目的主模块。为此,我们使用 [项目/属性] 选项:

在 [Startup Object] 中,我们指定 [Form1],即我们刚刚创建的窗体。要运行应用程序,我们使用 [Debug/Start] 选项:

5.2.5. 在命令提示符窗口中编译
现在,让我们尝试在 DOS 窗口中编译并运行此应用程序:
dos>dir form1.vb
14/03/2004 11:53 514 Form1.vb
dos>vbc /r:system.dll /r:system.windows.forms.dll form1.vb
vbc : error BC30420: 'Sub Main' cannot be found in 'Form1'.
编译器提示无法找到 [Main] 过程。确实,VS.NET 并未生成该过程。不过,我们在之前的示例中已经遇到过它。它的形式如下:
Shared Sub Main()
' launch application
Application.Run(New Form1) ' where Form1 is the form
End Sub
我们将上述代码添加到 [Form1.vb] 中并重新编译:
dos>vbc /r:system.dll /r:system.windows.forms.dll form2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
Form2.vb(41) : error BC30451: Le nom 'Application' n'est pas déclaré.
Application.Run(New Form2)
~~~~~~~~~~~
这次,[Application] 这个名称是未知的。这仅仅意味着我们尚未导入其命名空间 [System.Windows.Forms]。让我们添加以下语句:
然后重新编译:
这次运行成功了。让我们运行它:

创建并显示了一个 Form1 类型的窗体。您可以通过使用编译器的 /m 选项来避免添加 [Main] 过程,该选项允许您指定要执行的类(如果该类继承自 System.Windows.Forms):
/m:form2 选项指定要执行的类是名为 [form2] 的类。
5.2.6. 事件处理
窗体显示后,应用程序会监听窗体上发生的事件(点击、鼠标移动等),并执行由该窗体处理的事件。在此,我们的窗体除了由基类 Form 处理的事件(关闭按钮点击、最大化/最小化按钮点击、窗口调整大小、窗口移动等)外,不处理任何其他事件。生成的窗体使用了一个 components 属性,但该属性在任何地方都没有被使用。 此处的 Dispose 方法也是多余的。同样,所使用的某些命名空间(Collections、ComponentModel、Data)以及为项目 1 定义的命名空间也是多余的。因此,在此示例中,代码可以简化为以下形式:
Imports System
Imports System.Drawing
Imports System.Windows.Forms
Public Class Form1
Inherits System.Windows.Forms.Form
' manufacturer
Public Sub New()
' building the form with its components
InitializeComponent()
End Sub
Private Sub InitializeComponent()
' window size
Me.Size = New System.Drawing.Size(300, 300)
' window title
Me.Text = "Form1"
End Sub
Shared Sub Main()
' launch application
Application.Run(New Form1)
End Sub
End Class
5.2.7. 结论
现在,我们将直接采用 VS.NET 生成的代码,并仅添加我们自己的代码,具体来说是处理与窗体上各种控件相关的事件。
5.3. 包含输入框、按钮和标签的窗口
5.3.1. 图形设计
在上一个示例中,我们没有在窗口中放置任何组件。我们将创建一个名为 interface2 的新项目。为此,我们将按照之前描述的步骤来创建项目:

现在,让我们构建一个包含按钮、标签和输入框的窗口:
![]() |
字段如下:
编号 | 名称 | 类型 | 角色 |
1 | lblInput | 标签 | 一个标签 |
2 | txtInput | 文本框 | 一个输入框 |
3 | btnDisplay | 按钮 | 用于在对话框中显示txtSaisie文本框的内容 |
您可以按以下步骤构建此窗口:在窗口内部(任何组件之外)右键单击,然后选择“属性”选项以访问窗口的属性:

随后“属性”窗口将显示在右侧:

其中一些属性值得注意:
用于设置窗口的背景色 | |
用于设置窗口中图形或文本的颜色 | |
将菜单与窗口关联 | |
为窗口设置标题 | |
用于设置窗口类型 | |
用于设置窗口中文字的字体 | |
用于设置窗口名称 |
在此,我们设置 Text 和 Name 属性:
拉链与纽扣 - 1 | |
frmInputButtons |
使用“工具箱”栏
- 选择所需的组件
- 将它们拖放到窗口中并设置正确的尺寸
![]() | ![]() |
从“工具箱”中选定组件后,请使用 “Esc”键隐藏工具栏,然后拖放 并调整组件的大小。对以下三个 :Label、TextBox、Button。若要对齐并 调整组件的大小,请使用“格式”菜单: | ![]() |
格式设置过程如下:
“对齐”选项可让您对齐组件 | ![]() |
“设置相同大小”选项可确保 组件具有相同的高度或相同的 宽度: | ![]() |
例如,“水平间距”选项 用于将组件水平对齐, 。同样, “垂直间距”选项,用于垂直对齐。 “在表单中居中”选项允许您将 组件在窗口内水平或 垂直居中: | ![]() |
将组件正确 放置在窗口中后,请设置其属性。 要执行此操作,请右键单击组件并 选择“属性”选项:
选择该组件以打开 打开其“属性”窗口。在此窗口中, 修改以下属性: 名称:lblSaisie,文本:Saisie
选择该组件以打开其 属性窗口。在此窗口中,修改 以下属性: 名称:txtSaisie,文本:留空
名称:cmdAfficher,文本:显示
文本:输入框与按钮 - 1 | ![]() |
我们可以运行(Ctrl-F5)我们的项目 以初步查看该窗口 运行效果: | ![]() |
关闭窗口。我们还需要编写与“显示”按钮点击相关的过程。
5.3.2. 处理窗体事件
让我们看看可视化设计器生成的代码:
...
Public Class frmSaisiesBoutons
Inherits System.Windows.Forms.Form
' components
Private components As System.ComponentModel.Container = Nothing
Friend WithEvents btnAfficher As System.Windows.Forms.Button
Friend WithEvents lblsaisie As System.Windows.Forms.Label
Friend WithEvents txtsaisie As System.Windows.Forms.TextBox
' manufacturer
Public Sub New()
InitializeComponent()
End Sub
...
' component initialization
Private Sub InitializeComponent()
...
End Sub
End Class
首先,请注意组件的具体声明:
- Friend 关键字表示该组件对项目中的所有类都是可见的
- WithEvents 关键字表示该组件会生成事件。接下来我们将探讨如何处理这些事件
显示窗体的代码窗口(“视图/代码”或按 F7):
![]() |
上图所示的窗口中显示了两个下拉列表(1)和(2)。列表(1)是表单控件列表:

列表 (2) 显示了与 (1) 中所选组件相关的事件:

与该组件关联的事件之一以粗体显示(此处为 Click)。这是该组件的默认事件。您可以通过在设计窗口中双击该组件来访问此特定事件的处理程序。 随后,VB.NET 会在代码窗口中自动生成该事件处理程序的骨架,并将光标定位在其上。要访问其他事件的处理程序,请转到代码窗口,从列表 (1) 中选择该组件,然后从 (2) 中选择相应事件。随后,VB.NET 会生成该事件处理程序的骨架,或者如果该骨架已生成,则将光标定位在其上:
Private Sub btnAfficher_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAfficher.Click
...
End Sub
默认情况下,VB.NET 会为组件 C 的事件 E 命名事件处理程序。如果您愿意,可以更改此名称。但不建议这样做。VB 开发人员通常保留 VB 生成的名称,以确保所有 VB 程序中的命名一致性。btnAfficher_Click 过程与 btnAfficher 组件的 Click 事件的关联并非通过过程名称实现,而是通过 handles 关键字实现:
Handles btnAfficher.Click
在上面的代码中,handles 关键字指定该过程处理 btnAfficher 组件的 Click 事件。该事件处理程序有两个参数:
触发事件的对象(在此示例中为按钮) | |
一个 EventArgs 对象,详细描述了发生的事件 |
这里我们不会使用这些参数。剩下的就是完成代码了。这里,我们要显示一个包含 txtSaisie 字段内容的对话框:
Private Sub btnAfficher_Click(ByVal sender As Object, ByVal e As System.EventArgs)
' the text entered in the TxtSaisie input box is displayed
MessageBox.Show("texte saisi= " + txtsaisie.Text, "Vérification de la saisie", MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub
运行应用程序时,会出现以下情况:

5.3.3. 处理事件的另一种方法
对于 btnAfficher 按钮,VS.NET 生成了以下代码:
Private Sub InitializeComponent()
...
'
'btnAfficher
'
Me.btnAfficher.Location = New System.Drawing.Point(102, 48)
Me.btnAfficher.Name = "btnAfficher"
Me.btnAfficher.Size = New System.Drawing.Size(88, 24)
Me.btnAfficher.TabIndex = 2
Me.btnAfficher.Text = "Afficher"
...
End Sub
...
' event manager click on cmdAfficher button
Private Sub btnAfficher_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAfficher.Click
...
End Sub
我们还可以通过另一种方式将 btnAfficher_Click 过程与 btnAfficher 按钮的 Click 事件关联起来:
Private Sub InitializeComponent()
...
'
'btnAfficher
'
Me.btnAfficher.Location = New System.Drawing.Point(102, 48)
Me.btnAfficher.Name = "btnAfficher"
Me.btnAfficher.Size = New System.Drawing.Size(88, 24)
Me.btnAfficher.TabIndex = 2
Me.btnAfficher.Text = "Afficher"
AddHandler btnAfficher.Click, AddressOf btnAfficher_Click
...
End Sub
...
' event manager click on cmdAfficher button
Private Sub btnAfficher_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
...
End Sub
btnAfficher_Click 过程已丢失 Handles 关键字,因此失去了与 btnAfficher.Click 事件的关联。现在,这种关联是通过 AddHandler 关键字建立的:
AddHandler btnAfficher.Click, AddressOf btnAfficher_Click
上述代码将放置在窗体的 InitializeComponent 过程内,用于将名为 btnAfficher_Click 的过程与 btnAfficher.Click 事件关联。此外,btnAfficher 组件不再需要 WithEvents 关键字:
Friend btnAfficher As System.Windows.Forms.Button
这两种方法有什么区别?
- “handles”关键字仅允许在设计时将事件与过程关联。设计器预先知道过程 P 必须处理事件 E1、E2、...,并编写代码
Private Sub btnAfficher_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) handles E1, E2, ..., En
一个过程确实可以处理多个事件。
- addhandler 关键字允许在运行时将事件与过程关联起来。这在生产者-消费者事件框架中非常有用。一个对象会触发一个特定事件,该事件可能引起其他对象的关注。这些对象会订阅该生产者以接收事件(例如,温度超过临界阈值)。在应用程序的执行过程中,事件生产者将需要执行各种指令:
其中 E 是生产者产生的事件,P1 是属于消费该事件的各个对象之一。我们将在后面的章节中再次探讨生产者-消费者事件应用程序。
5.3.4. 结论
从所研究的两个项目中,我们可以得出结论:一旦使用 VS.NET 构建了图形用户界面,开发者的任务就是为该界面中需要管理的事件编写事件处理程序。从现在起,我们将仅展示这些处理程序的代码。
5.4. 一些有用的组件
接下来我们将展示若干利用常见组件的应用程序,以探讨其主要方法和属性。对于每个应用程序,我们将展示图形用户界面以及相关代码,特别是事件处理程序。
5.4.1. 窗体
我们将首先介绍一个关键组件:用于放置其他组件的窗体。我们之前已经介绍过其部分基本属性。在此,我们将重点关注几个重要的窗体事件。
表单正在加载 | |
表单正在关闭 | |
表单已关闭 |
Load 事件发生在表单显示之前。Closing 事件发生在表单关闭时。此时仍可通过编程方式阻止关闭操作。我们创建一个名为 Form1 的表单,其中不包含任何控件:

我们处理前三个事件:
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' initial form loading
MessageBox.Show("Evt Load", "Load")
End Sub
Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
' the form is closing
MessageBox.Show("Evt Closing", "Closing")
' confirmation requested
Dim réponse As DialogResult = MessageBox.Show("Voulez-vous vraiment quitter l'application", "Closing", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
If réponse = DialogResult.No Then
e.Cancel = True
End If
End Sub
Private Sub Form1_Closed(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Closed
' the form is closing
MessageBox.Show("Evt Closed", "Closed")
End Sub
我们使用 MessageBox 函数来接收各种 事件。当用户 关闭窗口时触发。 | ![]() |
随后我们会询问用户是否真的要退出应用程序: | ![]() |
如果用户回答“否”,我们会将该方法作为参数接收的 CancelEventArgs 的 Cancel 属性。 如果将该属性设置为 False,则 窗口的关闭操作将被取消;否则,程序将继续执行: | ![]() |
5.4.2. Label 控件和 TextBox 控件
我们已经接触过这两个组件。Label 是一个文本组件,TextBox 是一个输入框组件。它们的主要属性是 Text,该属性指代输入框的内容或标签文本。该属性是可读写的。TextBox 通常使用的事件是 TextChanged,它表示用户已修改了输入框。以下是一个使用 TextChanged 事件来跟踪输入框变化的示例:
![]() |
编号 | 类型 | 名称 | 角色 |
1 | 文本框 | txtInput | 输入字段 |
2 | 标签 | lblControl | 实时显示来自 1 的文本 |
3 | 按钮 | cmdClear | 用于清除字段 1 和 2 |
4 | 按钮 | cmdExit | 用于退出应用程序 |
该应用程序的相关代码是事件处理程序:
' click on btn quit
Private Sub cmdQuitter_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles cmdQuitter.Click
' click on the Quit button - exit the application
Application.Exit()
End Sub
' field modification txtSaisie
Private Sub txtSaisie_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles txtSaisie.TextChanged
' the content of TextBox has changed - copy it to Label lblControle
lblControle.Text = txtSaisie.Text
End Sub
' click on btn delete
Private Sub cmdEffacer_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles cmdEffacer.Click
' delete the contents of the input box
txtSaisie.Text = ""
End Sub
' a key has been pressed
Private Sub txtSaisie_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) _
Handles txtSaisie.KeyPress
' see which key has been pressed
Dim touche As Char = e.KeyChar
If touche = ControlChars.Cr Then
MessageBox.Show(txtSaisie.Text, "Contrôle", MessageBoxButtons.OK, MessageBoxIcon.Information)
e.Handled = True
End If
End Sub
请注意,在 cmdQuitter_Click 过程 中是如何终止应用程序的:Application.Exit()。以下示例使用了一个多行 TextBox:
![]() |
控件列表如下:
编号 | 类型 | 名称 | 角色 |
1 | 文本框 | txtMultiLines | 多行输入框 |
2 | 文本框 | txtAdd | 单行输入框 |
3 | 按钮 | btnAdd | 从 1 中减去 2 |
要使 TextBox 支持多行输入,请设置以下控件属性:
以支持多行文本 | |
指定控件是否应显示滚动条(水平、垂直、两边)或不显示(无) | |
若设置为 true,按 Enter 键将跳至下一行 | |
如果设置为 true,Tab 键将在文本中插入一个制表符 |
相关代码包括处理 [Add] 按钮点击事件的代码,以及处理 [txtAdd] 输入框变化的代码:
' évt btnAjouter_Click
Private Sub btnAjouter_Click1(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btnAjouter.Click
' add the content of txtAjout to that of txtMultilignes
txtMultilignes.Text &= txtAjout.Text
txtAjout.Text = ""
End Sub
' evt txtAjout_TextChanged
Private Sub txtAjout_TextChanged1(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles txtAjout.TextChanged
' set the state of the Add button
btnAjouter.Enabled = txtAjout.Text.Trim() <> ""
End Sub
5.4.3. 下拉列表框
![]() | ![]() |
ComboBox 组件是一个结合了下拉列表和输入字段的控件:用户既可以从(2)中选择一个项目,也可以在(1)中输入文本。根据 Style 属性,ComboBox 有三种类型:
带编辑框的非下拉列表 | |
带编辑框的下拉列表 | |
不带编辑框的下拉列表 |
默认情况下,ComboBox 的类型为 DropDown。要了解有关 ComboBox 类的更多信息,请在“帮助”索引(“帮助”/“索引”)中输入 ComboBox。ComboBox 类有一个构造函数:
Public Sub New() | 创建一个空的 ComboBox 对象 |
ComboBox 中的项目可通过 Items 属性访问:
这是一个索引属性,其中 Items(i) 表示下拉列表中的第 i 个项目。设 C 为一个下拉列表,C.Items 为其项目列表。我们有以下属性:
ComboBox 中的项目数量 | |
ComboBox 的第 i 个元素 | |
将对象 o 作为下拉列表的最后一个元素添加 | |
将一个对象数组添加到下拉列表的末尾 | |
将对象 o 插入下拉列表的第 i 个位置 | |
从组合框中移除第 i 个元素 | |
从组合框中移除对象 o | |
清除下拉列表中的所有项目 | |
返回对象 o 在下拉列表中的位置 i |
下拉列表通常包含字符串,因此它能包含对象这一事实可能令人感到惊讶。从视觉上看,确实如此。 如果 ComboBox 包含一个对象 obj,它将显示字符串 obj.ToString()。请记住,每个对象都继承了来自 Object 类的 ToString 方法,该方法返回一个“代表”该对象的字符串。下拉列表框 C 中选中的项目是 C.SelectedItem 或 C.Items(C.SelectedIndex),其中 C.SelectedIndex 是选中项的索引,从第一个项开始计数为 0。
当从下拉列表中选中一个项目时,会触发 SelectedIndexChanged 事件,该事件可用于检测组合框选中的变化。在下面的应用程序中,我们利用此事件来显示从列表中选中的项目。

此处仅展示与窗口相关的代码。在窗体的构造函数中,我们为下拉列表填充数据:
Public Sub New()
' création formulaire
InitializeComponent()
' remplissage combo
cmbNombres.Items.AddRange(New String() {"zéro", "un", "deux", "trois", "quatre"})
' nous sélectionnons le 1er élément de la liste
cmbNombres.SelectedIndex = 0
End Sub
我们处理下拉列表的 SelectedIndexChanged 事件,该事件表示已选中一个新项目:
Private Sub cmbNombres_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles cmbNombres.SelectedIndexChanged
' the selected item has changed - it is displayed
MessageBox.Show("Elément sélectionné : (" & cmbNombres.SelectedItem.ToString & "," & cmbNombres.SelectedIndex & ")", "Combo", MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub
5.4.4. ListBox 组件
我们建议构建以下界面:
![]() |
该窗口的组件如下:
编号 | 类型 | 名称 | 作用/属性 |
0 | 表单 | Form1 | 表单 - BorderStyle=FixedSingle |
1 | 文本框 | txtInput | 输入字段 |
2 | 按钮 | btnAdd | 用于将输入字段 1 的内容添加到列表 3 中的按钮 |
3 | 列表框 | listBox1 | 列表 1 |
4 | 列表框 | listBox2 | 列表 2 |
5 | 按钮 | btn1TO2 | 将列表 1 中选中的项目转移到列表 2 |
6 | 按钮 | cmd2T0 | 执行相反的操作 |
7 | 按钮 | btnClear1 | 清除列表 1 |
8 | 按钮 | btnClear2 | 清除列表 2 |
- 用户在字段 1 中输入文本。他们使用“添加”按钮 (2) 将其添加到列表 1 中。随后输入字段 (1) 会被清空,用户可以添加新项目。
- 用户可通过在其中一个列表中选中待转移的项目,并点击相应的转移按钮 5 或 6,将项目从一个列表转移到另一个列表。被转移的项目将添加到目标列表的末尾,并从源列表中移除。
- 用户可以双击列表 1 中的某项。该项随后会被转移到输入框中以便编辑,并从列表 1 中移除。
按钮的启用或禁用遵循以下规则:
- 只有当输入框中包含非空文本时,“添加”按钮才处于启用状态
- 仅当列表 1 中选中了某项时,用于将项目从列表 1 转移到列表 2 的按钮 5 才处于启用状态
- 只有当列表 2 中选中了某项时,用于将列表 2 中的内容转移到列表 1 的按钮 6 才会启用
- 仅当待清空的列表中包含项目时,用于清空列表 1 和列表 2 的按钮 7 和 8 才处于可用状态。
在上述条件下,应用程序启动时所有按钮必须处于禁用状态。这意味着按钮的 Enabled 属性必须设置为 false。这可以在设计时完成,系统会自动在 InitializeComponent 方法中生成相应的代码;或者,我们也可以像下面所示那样在构造函数中自行实现:
Public Sub New()
' initial form creation
InitializeComponent()
' additional initializations
' a number of buttons are disabled
btnAjouter.Enabled = False
btn1TO2.Enabled = False
btn2TO1.Enabled = False
btnEffacer1.Enabled = False
btnEffacer2.Enabled = False
End Sub
“添加”按钮的状态由文本输入框的内容控制。TextChanged 事件允许我们跟踪该内容的更改:
' change in field txtsaisie
Private Sub txtSaisie_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles txtSaisie.TextChanged
' the content of txtSaisie has changed
' the Add button is only lit if the entry is non-empty
btnAjouter.Enabled = txtSaisie.Text.Trim() <> ""
End Sub
传输按钮的状态取决于其所控制的列表中是否已选中某项:
' chgt selected item without listbox1
Private Sub listBox1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles listBox1.SelectedIndexChanged
' an item has been selected
' switch on the 1 to 2 transfer button
btn1TO2.Enabled = True
End Sub
' chgt selected item without listbox2
Private Sub listBox2_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles listBox2.SelectedIndexChanged
' an item has been selected
' switch on the 2 to 1 transfer button
btn2TO1.Enabled = True
End Sub
点击“添加”按钮时执行的代码如下:
' click on btn Add
Private Sub btnAjouter_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btnAjouter.Click
' add a new element to list 1
listBox1.Items.Add(txtSaisie.Text.Trim())
' raz de la saisie
txtSaisie.Text = ""
' List 1 is not empty
btnEffacer1.Enabled = True
' return focus to input box
txtSaisie.Focus()
End Sub
请注意 Focus 方法,它允许您将“焦点”设置在表单控件上。点击“清除”按钮时执行的代码如下:
' click on btn Delete1
Private Sub btnEffacer1_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btnEffacer1.Click
' delete list 1
listBox1.Items.Clear()
btnEffacer1.Enabled = False
End Sub
' click on btn delete2
Private Sub btnEffacer2_Click(ByVal sender As Object, ByVal e As System.EventArgs)
' delete list 2
listBox2.Items.Clear()
btnEffacer2.Enabled = False
End Sub
将选定项目从一个列表转移到另一个列表的代码:
' click on btn btn1to2
Private Sub btn1TO2_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btn1TO2.Click
' transfer the item selected in List 1 to List 2
transfert(listBox1, listBox2)
' delete buttons
btnEffacer2.Enabled = True
btnEffacer1.Enabled = listBox1.Items.Count <> 0
' transfer buttons
btn1TO2.Enabled = False
btn2TO1.Enabled = False
End Sub
' click on btn btn2to1
Private Sub btn2TO1_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btn2TO1.Click
' transfer item selected in List 2 to List 1
transfert(listBox2, listBox1)
' delete buttons
btnEffacer1.Enabled = True
btnEffacer2.Enabled = listBox2.Items.Count <> 0
' transfer buttons
btn1TO2.Enabled = False
btn2TO1.Enabled = False
End Sub
' transfer
Private Sub transfert(ByVal l1 As ListBox, ByVal l2 As ListBox)
' transfer selected item from list 1 to list l2
' a selected item?
If l1.SelectedIndex = -1 Then Return
' addition to l2
l2.Items.Add(l1.SelectedItem)
' deletion in l1
l1.Items.RemoveAt(l1.SelectedIndex)
End Sub
首先,我们创建一个方法
Private Sub transfert(ByVal l1 As ListBox, ByVal l2 As ListBox)
,用于将列表 l1 中选中的项目转移到列表 l2 中。这样,我们只需使用一个方法,而非两个方法,即可将项目从 ListBox1 转移到 ListBox2,或从 ListBox2 转移到 ListBox1。在执行转移操作之前,我们需要确保列表 l1 中确实选中了一个项目:
' un élément sélectionné ?
If l1.SelectedIndex = -1 Then Return
如果当前未选中任何项目,SelectedIndex 属性将为 -1。在以下过程
Private Sub btnXTOY_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btnXTOY.Click
我们将列表 X 的内容转移到列表 Y,并更新某些控件的状态以反映列表的新状态。
5.4.5. CheckBox 复选框,ButtonRadio 单选按钮
我们建议编写以下应用程序:
![]() |
窗口组件如下:
编号 | 类型 | 名称 | 作用 |
1 | 单选按钮 | radioButton1 radioButton2 单选按钮3 | 3 个单选按钮 |
2 | 复选框 | 复选框1 复选框2 复选框3 | 3 个复选框 |
3 | 列表框 | lstValues | 一个列表 |
如果我们依次创建这三个单选按钮,它们默认属于同一个组。因此,当其中一个被选中时,其余的则不会被选中。 对于这六个控件,我们关注的是 CheckChanged 事件,该事件表示复选框或单选按钮的状态已发生变化。这两种控件的状态均由布尔属性 Check 表示,当该属性为 true 时,表示控件已被选中。在此,我们使用了一个方法来处理所有六个 CheckChanged 事件;该方法显示:
' poster
Private Sub affiche(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles checkBox1.CheckedChanged, checkBox2.CheckedChanged, checkBox3.CheckedChanged, _
radioButton1.CheckedChanged, radioButton2.CheckedChanged, radioButton3.CheckedChanged
' displays radio button or checkbox status
' is this a checkbox?
If (TypeOf (sender) Is CheckBox) Then
Dim chk As CheckBox = CType(sender, CheckBox)
lstValeurs.Items.Insert(0, chk.Name & "=" & chk.Checked)
End If
' is it a radiobutton?
If (TypeOf (sender) Is RadioButton) Then
Dim rdb As RadioButton = CType(sender, RadioButton)
lstValeurs.Items.Insert(0, rdb.Name & "=" & rdb.Checked)
End If
End Sub
语法 TypeOf (sender) Is CheckBox 允许我们检查发送者对象是否为 CheckBox 类型。这样,我们就可以将其强制转换为发送者的确切类型。该方法会在 lstValeurs 列表中显示触发事件的组件名称及其 Checked 属性的值。 在运行时,我们可以看到点击单选按钮会触发两个 CheckChanged 事件:一个发生在之前被选中的按钮上,使其变为“未选中”;另一个发生在新选中的按钮上,使其变为“已选中”。
5.4.6. 滚动条控件
滚动条有几种类型:水平滚动条(hScrollBar)、 垂直滚动条(vScrollBar)以及数字上下滚动控件(NumericUpDown)。 | ![]() |
让我们创建以下应用程序:
![]() |
编号 | 类型 | 名称 | 角色 |
1 | hScrollBar | hScrollBar1 | 一个水平滚动条 |
2 | hScrollBar | hScrollBar2 | 一个水平滑块,其位置会跟随滑块 1 的变化 |
3 | 文本框 | txtValue | 显示水平滑块的数值 将 ReadOnly 设为 true 以禁止任何输入 |
4 | NumericUpDown | 增量器 | 允许您设置滑块 2 的值 |
- ScrollBar 滑块允许用户从滑块“带”所代表的整数值范围内选择一个值,光标沿该“带”移动。滑块的值可通过其 Value 属性获取。
- 对于水平滑块,左端代表范围的最小值,右端代表最大值,光标代表当前选定的值。对于垂直滑块,最小值由顶端表示,最大值由底端表示。这些值由 Minimum 和 Maximum 属性表示,默认值分别为 0 和 100。
- 点击滑块两端会根据点击位置(左端或右端)以一个增量(正值或负值)调整数值,该增量称为 SmallChange,默认值为 1。
- 点击滑块任一侧会根据点击的端点,使数值增加或减少一个增量(正值或负值),该设置称为 LargeChange,默认值为 10。
- 当您点击垂直滑块的顶端时,其数值会减小。这可能会让普通用户感到意外,因为他们通常期望数值“增加”。您可以通过将 SmallChange 和 LargeChange 属性设置为负值来解决此问题
- 这五个属性(Value、Minimum、Maximum、SmallChange、LargeChange)均可读写。
- 滑块的主要事件是指示值发生变化的事件:Scroll 事件。
NumericUpDown 组件与滑块类似:它同样具有 Minimum、Maximum 和 Value 属性,其默认值分别为 0、100 和 0。但在这种情况下,Value 属性会显示在一个作为控件组成部分的输入框中。除非控件的 ReadOnly 属性被设置为 true,否则用户可以自行修改该值。 增量值由 Increment 属性设定,其默认值为 1。NumericUpDown 组件的主要事件是用于通知值发生变化的事件:ValueChanged 事件。本应用程序的相关代码如下:
表单在构建过程中会进行格式化:
' manufacturer
Public Sub New()
' initial form creation
InitializeComponent()
' drive 2 is given the same characteristics as drive 1
hScrollBar2.Minimum = hScrollBar1.Value
hScrollBar2.Minimum = hScrollBar1.Minimum
hScrollBar2.Maximum = hScrollBar1.Maximum
hScrollBar2.LargeChange = hScrollBar1.LargeChange
hScrollBar2.SmallChange = hScrollBar1.SmallChange
' ditto for the incrementer
incrémenteur.Minimum = hScrollBar1.Value
incrémenteur.Minimum = hScrollBar1.Minimum
incrémenteur.Maximum = hScrollBar1.Maximum
incrémenteur.Increment = hScrollBar1.SmallChange
' give TextBox the value of drive 1
txtValeur.Text = "" & hScrollBar1.Value
End Sub
跟踪滑块 1 值变化的处理程序:
' hscrollbar1 drive management
Private Sub hScrollBar1_Scroll(ByVal sender As Object, ByVal e As System.Windows.Forms.ScrollEventArgs) _
Handles hScrollBar1.Scroll
' value change on drive 1
' its value is passed on to drive 2 and the TxtValeur textbox
hScrollBar2.Value = hScrollBar1.Value
txtValeur.Text = "" & hScrollBar1.Value
End Sub
用于跟踪滑块 2 值变化的处理程序:
' hscrollbar2 drive management
Private Sub hScrollBar2_Scroll(ByVal sender As Object, ByVal e As System.Windows.Forms.ScrollEventArgs) _
Handles hScrollBar2.Scroll
' inhibits all changes to drive 2
' forcing it to keep the value of drive 1
e.NewValue = hScrollBar1.Value
End Sub
用于跟踪滚动条变化的处理程序:
' incremental management
Private Sub incrémenteur_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles incrémenteur.ValueChanged
' set the value of controller 2
hScrollBar2.Value = CType(incrémenteur.Value, Integer)
End Sub
5.5. 鼠标事件
在容器中绘制时,了解鼠标位置非常重要,例如在点击时显示一个点。鼠标移动会在鼠标所在的容器中触发事件。
![]() | ![]() |
鼠标刚刚进入控件区域 | |
鼠标刚刚离开控件区域 | |
鼠标正在控件区域内移动 | |
鼠标左键被按下 | |
释放鼠标左键 | |
用户将对象拖放到控件上 | |
用户在拖动对象时进入控件区域 | |
用户在拖动对象时离开控件区域 | |
用户在拖动对象时移出了控件区域 |
以下是一个程序,可帮助您更好地理解各种鼠标事件何时发生:
![]() |
编号 | 类型 | 名称 | 角色 |
1 | 标签 | lblPosition | 用于在表单 1、列表 2 或按钮 3 中显示鼠标位置 |
2 | 列表框 | lstEvts | 用于显示除 MouseMove 以外的鼠标事件 |
3 | Button | btnClear | 用于清除 2 的内容 |
事件处理程序如下。为了跟踪鼠标在三个控件上的移动,我们编写一个单一的处理程序:
' évt form1_mousemove
Private Sub Form1_MouseMove(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles MyBase.MouseMove, lstEvts.MouseMove, btnEffacer.MouseMove, lstEvts.MouseMove
' mvt mouse - displays its (X,Y) coordinates
lblPosition.Text = "(" & e.X & "," & e.Y & ")"
End Sub
需要注意的是,每次鼠标进入控件区域时,其坐标系都会发生变化。其原点 (0,0) 即为当前所在控件的左上角。因此,在执行过程中,当鼠标从窗体移动到按钮时,坐标的变化会清晰可见。为了更好地观察鼠标作用域内的这些变化,您可以使用控件的 Cursor 属性:

该属性允许您设置鼠标进入控件区域时的光标形状。因此,在我们的示例中,我们为表单本身设置了默认光标,为列表 2 设置了手形光标,为按钮 3 设置了无光标,如下图所示。



在 [InitializeComponent] 方法中,这些选项生成的代码如下:
Me.lstEvts.Cursor = System.Windows.Forms.Cursors.Hand
Me.btnEffacer.Cursor = System.Windows.Forms.Cursors.No
此外,为了检测鼠标在列表 2 上的进入和离开,我们处理了该列表的 MouseEnter 和 MouseLeave 事件:
' evt lstEvts_MouseEnter
Private Sub lstEvts_MouseEnter(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles lstEvts.MouseEnter
affiche("MouseEnter sur liste")
End Sub
' evt lstEvts_MouseLeave
Private Sub lstEvts_MouseLeave(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles lstEvts.MouseLeave
affiche("MouseLeave sur liste")
End Sub
' poster
Private Sub affiche(ByVal message As String)
' the message is displayed at the top of the event list
lstEvts.Items.Insert(0, message)
End Sub

为了处理表单上的点击事件,我们处理 MouseDown 和 MouseUp 事件:
' évt Form1_MouseDown
Private Sub Form1_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles MyBase.MouseDown
affiche("MouseDown sur formulaire")
End Sub
' évt Form1_MouseUp
Private Sub Form1_MouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles MyBase.MouseUp
affiche("MouseUp sur formulaire")
End Sub

最后,是“删除”按钮的 Click 事件处理程序代码:
' évt btnEffacer_Click
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles btnEffacer.Click
' clears the event list
lstEvts.Items.Clear()
End Sub
5.6. 创建一个带菜单的窗口
现在让我们看看如何创建一个带有菜单的窗口。我们将创建以下窗口:
![]() |
控件 1 是一个名为 txtStatut 的只读 TextBox(ReadOnly=true)。菜单树如下:
![]() | ![]() |
菜单选项与其他可视化组件一样,都是控件,具有属性与事件。例如,菜单选项 A1 的属性表:

本示例中使用了两个属性:
菜单控件的名称 | |
菜单选项的标签 |
本示例中各个菜单选项的属性如下:
名称 | 文本 |
mnuA | 选项 A |
mnuA1 | A1 |
mnuA2 | A2 |
mnuA3 | A3 |
mnuB | 选项 B |
mnuB1 | B1 |
mnuSep1 | - (分隔符) |
mnuB2 | B2 |
mnuB3 | B3 |
mnuB31 | B31 |
mnuB32 | B32 |
要创建菜单,请从“工具箱”栏中选择“MainMenu”组件:

随后,表单上将出现一个带有“在此输入”标签的空框菜单。只需在此处输入各种菜单选项即可:

若要在两个选项之间插入分隔符(如上图 B1 和 B2 选项之间所示),请将光标定位在菜单中希望出现分隔符的位置,右键单击,然后选择“插入分隔符”选项:

如果您使用 Ctrl+F5 运行应用程序,将看到一个带有菜单的表单,但该菜单目前尚无功能。菜单选项被视为组件:它们具有属性和事件。在 [代码窗口] 中,选择 mnuA1 组件,然后选择相关的事件:

如果你触发上面的 Click 事件,VS.NET 会自动生成以下过程:
Private Sub mnuA1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuA.Click
....
End Sub
我们可以按照这种方式处理所有菜单选项。这里,同一个过程可以用于所有选项。因此,我们将前面的过程重*命名为 display*,并声明它所处理的事件:
Private Sub affiche(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles mnuA1.Click, mnuA2.Click, mnuB1.Click, mnuB2.Click, mnuB31.Click, mnuB32.Click
' displays the name of the selected submenu in the TextBox
txtStatut.Text = (CType(sender, MenuItem)).Text
End Sub
在此方法中,我们仅显示触发该事件的菜单选项的 Text 属性。事件源发送者的类型为 Object。菜单选项的类型为 MenuItem,因此我们必须在此处将 Object 强制转换为 MenuItem。运行应用程序并选择选项 A1,即可看到以下消息:

除了 Display 方法外,该应用程序的相关代码还包括在窗体构造函数(InitializeComponent)中构建菜单的代码:
Private Sub InitializeComponent()
Me.mainMenu1 = New System.Windows.Forms.MainMenu
Me.mnuA = New System.Windows.Forms.MenuItem
Me.mnuA1 = New System.Windows.Forms.MenuItem
Me.mnuA2 = New System.Windows.Forms.MenuItem
Me.mnuA3 = New System.Windows.Forms.MenuItem
Me.mnuB = New System.Windows.Forms.MenuItem
Me.mnuB1 = New System.Windows.Forms.MenuItem
Me.mnuB2 = New System.Windows.Forms.MenuItem
Me.mnuB3 = New System.Windows.Forms.MenuItem
Me.mnuB31 = New System.Windows.Forms.MenuItem
Me.mnuB32 = New System.Windows.Forms.MenuItem
Me.txtStatut = New System.Windows.Forms.TextBox
Me.mnuSep1 = New System.Windows.Forms.MenuItem
Me.SuspendLayout()
'
' mainMenu1
'
Me.mainMenu1.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.mnuA, Me.mnuB})
'
' mnuA
'
Me.mnuA.Index = 0
Me.mnuA.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.mnuA1, Me.mnuA2, Me.mnuA3})
Me.mnuA.Text = "Options A"
'
' mnuA1
'
Me.mnuA1.Index = 0
Me.mnuA1.Text = "A1"
'
' mnuA2
'
Me.mnuA2.Index = 1
Me.mnuA2.Text = "A2"
'
' mnuA3
'
Me.mnuA3.Index = 2
Me.mnuA3.Text = "A3"
'
' mnuB
'
Me.mnuB.Index = 1
Me.mnuB.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.mnuB1, Me.mnuSep1, Me.mnuB2, Me.mnuB3})
Me.mnuB.Text = "Options B"
'
' mnuB1
'
Me.mnuB1.Index = 0
Me.mnuB1.Text = "B1"
'
' mnuB2
'
Me.mnuB2.Index = 2
Me.mnuB2.Text = "B2"
'
' mnuB3
'
Me.mnuB3.Index = 3
Me.mnuB3.MenuItems.AddRange(New System.Windows.Forms.MenuItem() {Me.mnuB31, Me.mnuB32})
Me.mnuB3.Text = "B3"
'
' mnuB31
'
Me.mnuB31.Index = 0
Me.mnuB31.Text = "B31"
'
' mnuB32
'
Me.mnuB32.Index = 1
Me.mnuB32.Text = "B32"
'
' txtStatut
'
Me.txtStatut.Location = New System.Drawing.Point(8, 8)
Me.txtStatut.Name = "txtStatut"
Me.txtStatut.ReadOnly = True
Me.txtStatut.Size = New System.Drawing.Size(112, 20)
Me.txtStatut.TabIndex = 0
Me.txtStatut.Text = ""
'
' mnuSep1
'
Me.mnuSep1.Index = 1
Me.mnuSep1.Text = "-"
'
' Form1
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(136, 42)
Me.Controls.Add(txtStatut)
Me.Menu = Me.mainMenu1
Me.Name = "Form1"
Me.Text = "Menus"
Me.ResumeLayout(False)
End Sub
请注意将菜单与窗体关联的语句:
Me.Menu = Me.mainMenu1
5.7. 非可视化组件
接下来我们将介绍一些非视觉组件:这些组件在设计阶段会被使用,但在运行时不可见。
5.7.1. OpenFileDialog 和 SaveFileDialog 对话框
我们将构建以下应用程序:
![]() |
控件如下:
编号 | 类型 | 名称 | 角色 |
1 | 多行文本框 | txtText | 用户输入的文本或从文件加载的文本 |
2 | 按钮 | btnSave | 将第 1 项中的文本保存到文本文件中 |
3 | 按钮 | btnLoad | 允许您将文本文件的内容加载到 1 |
4 | 按钮 | btnClear | 清除 1 的内容 |
使用了两个非视觉控件:

当从“工具箱”中拖动它们并将其放到窗体上时,它们会被放置在窗体底部的独立区域中。从“工具箱”中拖动“对话框”组件:

“清除”按钮的代码很简单:
Private Sub btnEffacer_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btnEffacer.Click
' clear the input box
txtTexte.Text = ""
End Sub
SaveFileDialog 类的定义如下:

它继承自多个类层级。在其众多属性和方法中,我们将重点关注以下内容:
对话框中文件类型下拉列表中提供的文件类型 | |
上述列表中默认显示的文件类型的索引。从 0 开始。 | |
保存文件时初始显示的文件夹 | |
用户指定的保存文件名 | |
一个用于显示保存对话框的方法。返回一个 DialogResult 对象。 |
ShowDialog 方法会显示一个类似于以下内容的对话框:
![]() |
由 Filter 属性生成的下拉列表。默认文件类型由 FilterIndex 决定 | |
当前目录,若已指定 InitialDirectory 属性,则由该属性设定 | |
用户所选或直接输入的文件名。该名称将可在 FileName 属性中获取 | |
“保存/取消”按钮。若使用“保存”按钮,ShowDialog 函数将返回结果 DialogResult.OK |
保存过程可编写如下:
Private Sub btnSauvegarder_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btnSauvegarder.Click
' save the input box in a text file
' set the savefileDialog1 dialog box
saveFileDialog1.InitialDirectory = Application.ExecutablePath
saveFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*"
saveFileDialog1.FilterIndex = 0
' display the dialog box and retrieve the result
If saveFileDialog1.ShowDialog() = DialogResult.OK Then
' retrieve the file name
Dim nomFichier As String = saveFileDialog1.FileName
Dim fichier As StreamWriter = Nothing
Try
' open the file for writing
fichier = New StreamWriter(nomFichier)
' we write the text inside
fichier.Write(txtTexte.Text)
Catch ex As Exception
' problem
MessageBox.Show("Problème à l'écriture du fichier (" + ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return
Finally
' close the file
Try
fichier.Close()
Catch
End Try
End Try
End If
End Sub
- 我们将初始目录设置为包含应用程序可执行文件的目录:
- 我们将要显示的文件类型设置为
请注意过滤器语法:filter1|filter2|..|filteren,其中 filteri = 文本|文件类型。在此,用户可以在 *.txt 和 *.* 文件之间进行选择。
- 我们将文件类型设置为在列表开头显示
在此,系统将首先向用户显示 *.txt 格式的文件。
- 显示对话框并获取其结果
If saveFileDialog1.ShowDialog() = DialogResult.OK Then
- 在对话框显示期间,用户无法访问主窗体(即所谓的模态对话框)。用户设置待保存文件的名称,并通过单击“保存”按钮、“取消”按钮或直接关闭对话框来退出对话框。只有当用户使用“保存”按钮退出对话框时,ShowDialog 方法的结果才会是 DialogResult.OK。
- 完成上述操作后,待创建文件的名称现已存储在 saveFileDialog1 对象的 FileName 属性中。随后,我们回归到创建文本文件的标准流程。我们将 TextBox 的内容(txtTexte.Text)写入文件,同时处理可能发生的任何异常。
OpenFileDialog 类与 SaveFileDialog 类非常相似,且源自相同的类继承体系。在这些属性和方法中,我们将重点关注以下内容:
对话框中文件类型下拉列表所提供的文件类型 | |
上述列表中默认显示的文件类型的索引。从 0 开始。 | |
初始显示用于搜索要打开文件的目录 | |
用户指定的要打开的文件名 | |
一个用于显示保存对话框的方法。返回一个 DialogResult 对象。 |
ShowDialog 方法会显示一个类似于以下内容的对话框:
![]() |
由 Filter 属性生成的下拉列表。默认文件类型由 FilterIndex 决定 | |
当前文件夹,若已指定 InitialDirectory 属性,则由该属性设定 | |
用户所选或直接输入的文件名。该名称将显示在 FileName 属性中 | |
“打开”/“取消”按钮。如果使用“打开”按钮,ShowDialog 函数将返回结果 DialogResult.OK |
该开放过程可写为如下形式:
Private Sub btnCharger_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btnCharger.Click
' load a text file into the input box
' set the openfileDialog1 dialog box
openFileDialog1.InitialDirectory = Application.ExecutablePath
openFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*"
openFileDialog1.FilterIndex = 0
' display the dialog box and retrieve the result
If openFileDialog1.ShowDialog() = DialogResult.OK Then
' retrieve the file name
Dim nomFichier As String = openFileDialog1.FileName
Dim fichier As StreamReader = Nothing
Try
' open the file in read mode
fichier = New StreamReader(nomFichier)
' read the entire file and put it in the TextBox
txtTexte.Text = fichier.ReadToEnd()
Catch ex As Exception
' problem
MessageBox.Show("Problème à la lecture du fichier (" + ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return
Finally
' close the file
Try
fichier.Close()
Catch
End Try
End Try
End If
End Sub
- 我们将初始目录设置为包含应用程序可执行文件的目录:
- 我们将要显示的文件类型设置为
- 设置首先显示的文件类型
这样,系统会首先向用户显示 *.txt 文件。
- 显示对话框并获取其结果
If openFileDialog1.ShowDialog() = DialogResult.OK Then
在对话框显示期间,用户无法访问主窗体(即所谓的模态对话框)。用户指定要打开的文件名,并通过单击“打开”按钮、“取消”按钮或关闭对话框来退出对话框。只有当用户使用“打开”按钮退出对话框时,ShowDialog 方法的结果才会是 DialogResult.OK。
- 完成上述操作后,待创建文件的名称现已存储在 openFileDialog1 对象的 FileName 属性中。随后,我们回到读取文本文件的标准流程。请注意以下允许读取整个文件的方法:
- 文件内容将被放入 txtTexte 文本框中。我们会处理可能发生的任何异常。
5.7.2. FontColor 和 ColorDialog 对话框
我们延续上一个示例,引入两个新按钮:
![]() |
否 | 类型 | 名称 | 角色 |
6 | 按钮 | btnColor | 用于设置文本框的文字颜色 |
7 | 按钮 | btnFont | 用于设置文本框的字体 |
我们在窗体上放置一个 ColorDialog 控件和一个 FontDialog 控件:

FontDialog 和 ColorDialog 类都拥有一个与 OpenFileDialog 和 SaveFileDialog 类的 ShowDialog 方法类似的 ShowDialog 方法。ColorDialog 类的 ShowDialog 方法允许您选择颜色:

如果用户通过“确定”按钮关闭对话框,则 ShowDialog 方法的结果为 DialogResult.OK,所选颜色将存储在所用 ColorDialog 对象的 Color 属性中。FontDialog 类的 ShowDialog 方法允许您选择字体:

如果用户通过单击“确定”按钮关闭对话框,ShowDialog 方法的结果为 DialogResult.OK,所选字体将存储在所用 FontDialog 对象的 Font 属性中。我们已准备好处理“颜色”和“字体”按钮点击的代码:
Private Sub btnCouleur_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btnCouleur.Click
' choice of text color
If colorDialog1.ShowDialog() = DialogResult.OK Then
' change the forecolor property of TextBox
txtTexte.ForeColor = colorDialog1.Color
End If
End Sub
Private Sub btnPolice_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btnPolice.Click
' font selection
If fontDialog1.ShowDialog() = DialogResult.OK Then
' change the font property of TextBox
txtTexte.Font = fontDialog1.Font
End If
End Sub
5.7.3. 定时器
在此,我们建议编写以下应用程序:
![]() |
编号 | 类型 | 名称 | 角色 |
1 | TextBox, ReadOnly=true | txtChrono | 显示一个计时器 |
2 | 按钮 | btnStopStart | 秒表的停止/开始按钮 |
3 | 计时器 | timer1 | 每秒触发一次事件的组件 |
计时器正在运行:

计时器已停止:

为了每秒更新 txtChrono 文本框的内容,我们需要一个每秒生成一次事件的组件,我们可以拦截该事件来更新秒表的显示。这个组件就是 Timer:

将该组件添加到窗体(位于“非视觉组件”部分)后,窗体的构造函数中会创建一个 Timer 对象。System.Windows.Forms.Timer 类的定义如下:

在其属性中,我们将仅关注以下内容:
触发Tick事件所需的毫秒数。 | |
在间隔毫秒数结束时触发的事件 | |
将计时器设置为活动状态(true)或非活动状态(false) |
在本示例中,定时器名为 timer1,且 timer1.Interval 设置为 1000 毫秒(1 秒)。因此,Tick 事件将每秒触发一次。点击“开始/停止”按钮将由以下过程处理:
Private Sub btnArretMarche_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btnArretMarche.Click
' off or on?
If btnArretMarche.Text = "Marche" Then
' we note the start time
début = DateTime.Now
' we display it
txtChrono.Text = "00:00:00"
' start timer
timer1.Enabled = True
' change the button label
btnArretMarche.Text = "Arrêt"
' end
Return
End If '
If btnArretMarche.Text = "Arrêt" Then
' timer off
timer1.Enabled = False
' change the button label
btnArretMarche.Text = "Marche"
' end
Return
End If
End Sub
Private Sub timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles timer1.Tick
' a second has passed
Dim maintenant As DateTime = DateTime.Now
Dim durée As TimeSpan = DateTime.op_Subtraction(maintenant, début)
txtChrono.Text = "" + durée.Hours.ToString("d2") + ":" + durée.Minutes.ToString("d2") + ":" + durée.Seconds.ToString("d2")
End Sub
“开始/停止”按钮的标签可能是“停止”或“开始”。因此,我们需要检查该标签以确定后续操作。
- 如果标签为“Start”,我们将开始时间存储在表单对象的全局变量中,启动计时器(Enabled=true),并将按钮标签更改为“Stop”。
- 如果标签为“停止”,则停止计时器(Enabled=false),并将按钮标签更改为“开始”。
Public Class Timer1
Inherits System.Windows.Forms.Form
Private WithEvents timer1 As System.Windows.Forms.Timer
Private WithEvents btnArretMarche As System.Windows.Forms.Button
Private components As System.ComponentModel.IContainer
Private WithEvents txtChrono As System.Windows.Forms.TextBox
Private WithEvents label1 As System.Windows.Forms.Label
' instance variables
Private début As DateTime
上文中的 start 属性在该类的所有方法中均有效。我们还需要处理 timer1 对象上的 Tick 事件,该事件每秒触发一次:
Private Sub timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles timer1.Tick
' a second has passed
Dim maintenant As DateTime = DateTime.Now
Dim durée As TimeSpan = DateTime.op_Subtraction(maintenant, début)
txtChrono.Text = "" + durée.Hours.ToString("d2") + ":" + durée.Minutes.ToString("d2") + ":" + durée.Seconds.ToString("d2")
End Sub
我们计算自秒表启动以来经过的时间。我们获得一个代表持续时间的 TimeSpan 对象。该值必须以 hh:mm:ss 格式显示在计时器中。为此,我们使用 TimeSpan 对象的 Hours、Minutes 和 Seconds 属性,它们分别代表持续时间的小时、分钟和秒数。我们使用 ToString("d2") 格式进行显示,以确保以两位数形式呈现。
5.8. 税费计算示例
我们回到之前已讲解过两次的 IMPOTS 应用程序。现在,我们将为其添加一个图形用户界面:
![]() |
控件如下所示
编号 | 类型 | 名称 | 角色 |
单选按钮 | rdYes | 已婚时勾选 | |
单选按钮 | rdNo | 未婚时勾选 | |
数字上下滑块 | incChildren | 纳税人子女的数量 最小值=0,最大值=20,增量=1 | |
文本框 | txtSalary | 纳税人的年薪(单位:F) | |
文本框 | txtTaxes | 应缴税额 只读=true | |
按钮 | btnCalculate | 开始计算税款 | |
按钮 | btnClear | 在加载时将表单重置为初始状态 | |
按钮 | btnExit | 用于退出应用程序 |
操作规则
- 只要薪资字段为空,“计算”按钮将保持禁用状态
- 如果在运行计算时,发现薪资有误,系统将报告错误:

程序代码如下所示。它使用了“类”一章中创建的 Impot 类。此处未复制 VS.NET 自动生成的部分代码。
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports System.Drawing
Imports System.Collections
Imports System.ComponentModel
Imports System.Windows.Forms
Imports System.Data
' form class
Public Class frmImpots
Inherits System.Windows.Forms.Form
Private WithEvents label1 As System.Windows.Forms.Label
Private WithEvents rdOui As System.Windows.Forms.RadioButton
Private WithEvents rdNon As System.Windows.Forms.RadioButton
Private WithEvents label2 As System.Windows.Forms.Label
Private WithEvents txtSalaire As System.Windows.Forms.TextBox
Private WithEvents label3 As System.Windows.Forms.Label
Private WithEvents label4 As System.Windows.Forms.Label
Private WithEvents groupBox1 As System.Windows.Forms.GroupBox
Private WithEvents btnCalculer As System.Windows.Forms.Button
Private WithEvents btnEffacer As System.Windows.Forms.Button
Private WithEvents btnQuitter As System.Windows.Forms.Button
Private WithEvents txtImpots As System.Windows.Forms.TextBox
Private components As System.ComponentModel.Container = Nothing
Private WithEvents incEnfants As System.Windows.Forms.NumericUpDown
' data tables required for tax calculation
Private limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
Private coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.55D, 0.5D, 0.6D, 0.65D}
Private coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}
' tax object
Private objImpôt As impot = Nothing
Public Sub New()
InitializeComponent()
' form initialization
btnEffacer_Click(Nothing, Nothing)
btnCalculer.Enabled = False
' tax object creation
Try
objImpôt = New impot(limites, coeffR, coeffN)
Catch ex As Exception
MessageBox.Show("Impossible de créer l'objet impôt (" + ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error)
' inhibit the salary entry field
txtSalaire.Enabled = False
End Try 'try-catch
End Sub
Protected Overloads Sub Dispose(ByVal disposing As Boolean)
....
End Sub
Private Sub InitializeComponent()
Me.btnQuitter = New System.Windows.Forms.Button
Me.groupBox1 = New System.Windows.Forms.GroupBox
Me.btnEffacer = New System.Windows.Forms.Button
Me.btnCalculer = New System.Windows.Forms.Button
Me.txtSalaire = New System.Windows.Forms.TextBox
Me.label1 = New System.Windows.Forms.Label
Me.label2 = New System.Windows.Forms.Label
Me.label3 = New System.Windows.Forms.Label
Me.rdNon = New System.Windows.Forms.RadioButton
Me.txtImpots = New System.Windows.Forms.TextBox
Me.label4 = New System.Windows.Forms.Label
Me.rdOui = New System.Windows.Forms.RadioButton
Me.incEnfants = New System.Windows.Forms.NumericUpDown
Me.groupBox1.SuspendLayout()
CType(Me.incEnfants, System.ComponentModel.ISupportInitialize).BeginInit()
Me.SuspendLayout()
....
End Sub 'InitializeComponent
Public Shared Sub Main()
Application.Run(New frmImpots)
End Sub 'Main
Private Sub btnEffacer_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btnEffacer.Click
' raz du formulaire
incEnfants.Value = 0
txtSalaire.Text = ""
txtImpots.Text = ""
rdNon.Checked = True
End Sub 'btnEffacer_Click
Private Sub txtSalaire_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles txtSalaire.TextChanged
' calculate button status
btnCalculer.Enabled = txtSalaire.Text.Trim() <> ""
End Sub 'txtSalaire_TextChanged
Private Sub btnQuitter_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btnQuitter.Click
' end application
Application.Exit()
End Sub 'btnQuitter_Click
Private Sub btnCalculer_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles btnCalculer.Click
' is the salary correct?
Dim intSalaire As Integer = 0
Try
' salary recovery
intSalaire = Integer.Parse(txtSalaire.Text)
' it must be >=0
If intSalaire < 0 Then
Throw New Exception("")
End If
Catch ex As Exception
' error msg
MessageBox.Show(Me, "Salaire incorrect", "Erreur de saisie", MessageBoxButtons.OK, MessageBoxIcon.Error)
' focus on wrong field
txtSalaire.Focus()
' select text for input field
txtSalaire.SelectAll()
' back to visual interface
Return
End Try 'try-catch
' salary is correct - tax is calculated
txtImpots.Text = "" & CLng(objImpôt.calculer(rdOui.Checked, CInt(incEnfants.Value), intSalaire))
End Sub 'btnCalculer_Click
End Class
这里我们使用了 impots.dll 程序集,它是第 2 章中 impots 类的编译结果。请注意,该程序集可以通过以下命令在控制台模式下生成
该命令会生成名为 impots.dll 的文件,即所谓的程序集。该程序集随后可在各种项目中使用。在此,我们在 VS.NET 下的项目中,通过项目属性窗口进行操作:

要添加引用(即程序集),我们右键单击上方的“引用”组,选择[添加引用]选项,并指定我们已放置在项目文件夹中的[impots.dll]程序集:
dos>dir
01/03/2004 14:39 9 250 gui_impots.vb
01/03/2004 14:37 4 096 impots.dll
01/03/2004 14:41 12 288 gui_impots.exe
一旦将程序集 [impots.dll] 添加到项目中,项目就会识别 [impots] 类。此前,项目并不识别该类。另一种方法是将源文件 impots.vb 添加到项目中。要执行此操作,请在项目属性窗口中右键单击该项目,选择 [添加/添加现有项] 选项,然后指定 impots.vb 文件。















































