Skip to content

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

上述代码先编译,然后执行

dos>vbc /r:system.dll /r:system.drawing.dll /r:system.windows.forms.dll frm1.vb

dos>frm1

执行后显示以下窗口:

Image

图形用户界面通常继承自基类 System.Windows.Forms.Form

Public Class Form1
    Inherits 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 事件,如下所示:

Public Event Click As EventHandler

其中 EventHandler 是一个具有以下签名的“委托”函数:

Public Delegate Sub EventHandler(ByVal sender As Object,ByVal e As EventArgs)

这意味着按钮上 [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

我们只是显示一条消息:

Image

该类经过编译并执行:

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. 初始项目创建

  1. 启动 VS.NET,然后选择“文件”>“新建”>“项目”

Image

  1. 指定项目的属性
    1. 选择要构建的项目类型;此处为 VB.NET 项目 (1)
    2. 选择要构建的应用程序类型;此处为 Windows 应用程序 (2)
    3. 指定要放置项目子文件夹的文件夹(3)
    4. 输入项目名称(4)。这也将是存放项目文件的文件夹名称
    5. 该文件夹的名称将显示在 (5) 中
  1. 随后将在 i4 文件夹下创建若干文件夹和文件:
project1文件夹的子文件夹

在这些文件中,只有一个与我们相关:form1.cs 文件,它是 VS.NET 创建的窗体对应的源文件。我们稍后会再回到这个文件。

5.2.2. VS.NET 的界面窗口

VS.NET 界面现在显示了我们 i4 项目的某些元素:

我们有一个图形用户界面设计窗口:

通过从工具栏(工具箱 2)中拖动控件并将其拖放到窗口区域(1)上,我们可以构建一个图形用户界面。如果将鼠标悬停在“工具箱”上,它会展开并显示多个控件:

Image

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

Image

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

Image

您也可以直接从“解决方案资源管理器”窗口访问这两者:

 

打开的窗口会在主设计窗口中“堆叠”显示:

Image

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

Image

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

Image

其中包含上述主要窗口及其键盘快捷键。

5.2.3. 运行项目

尽管我们尚未编写任何代码,但已有一个可执行的项目。按 F5选择“调试/开始”来运行它。我们将看到以下窗口:

Image

该窗口可以最大化、最小化、调整大小以及关闭。

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

Public Class Form1
    Inherits 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 命名空间中,是一个结构体。要运行此应用程序,我们需要定义项目的主模块。为此,我们使用 [项目/属性] 选项:

Image

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

Image

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]。让我们添加以下语句:

Imports System.Windows.Forms

然后重新编译:

dos>vbc /r:system.dll /r:system.windows.forms.dll form1.vb

这次运行成功了。让我们运行它:

dos>dir
14/03/2004  11:53               514 Form1.vb
14/03/2004  12:15             3 584 Form1.exe

dos>form1

Image

创建并显示了一个 Form1 类型的窗体。您可以通过使用编译器的 /m 选项来避免添加 [Main] 过程,该选项允许您指定要执行的类(如果该类继承自 System.Windows.Forms):

dos>vbc /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll /m:form2 form2.vb

/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 的新项目。为此,我们将按照之前描述的步骤来创建项目:

Image

现在,让我们构建一个包含按钮、标签和输入框的窗口:

字段如下:

编号
名称
类型
角色
1
lblInput
标签
一个标签
2
txtInput
文本框
一个输入框
3
btnDisplay
按钮
用于在对话框中显示txtSaisie文本框的内容

您可以按以下步骤构建此窗口:在窗口内部(任何组件之外)右键单击,然后选择“属性”选项以访问窗口的属性:

Image

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

Image

其中一些属性值得注意:

背景色
用于设置窗口的背景色
ForeColor
用于设置窗口中图形或文本的颜色
菜单
将菜单与窗口关联
文本
为窗口设置标题
FormBorderStyle
用于设置窗口类型
字体
用于设置窗口中文字的字体
名称
用于设置窗口名称

在此,我们设置 TextName 属性:

Text
拉链与纽扣 - 1
名称
frmInputButtons

使用“工具箱”栏

  • 选择所需的组件
  • 将它们拖放到窗口中并设置正确的尺寸
从“工具箱”中选定组件后,请使用
“Esc”键隐藏工具栏,然后拖放
并调整组件的大小。对以下三个
LabelTextBoxButton。若要对齐并
调整组件的大小,请使用“格式”菜单:
格式设置过程如下:
  1. 选择需要一起设置格式的各个组件(按住 Ctrl 键)
  2. 选择所需的格式设置选项
“对齐”选项可让您对齐组件
“设置相同大小”选项可确保
组件具有相同的高度或相同的
宽度:
例如,“水平间距”选项
用于将组件水平对齐,
。同样,
“垂直间距”选项,用于垂直对齐。
“在表单中居中”选项允许您将
组件在窗口内水平或
垂直居中:
将组件正确
放置在窗口中后,请设置其属性。
要执行此操作,请右键单击组件并
选择“属性”选项:
  • 标签 1
选择该组件以打开
打开其“属性”窗口。在此窗口中,
修改以下属性:
名称:lblSaisie,文本:Saisie
  • 输入字段 2 (TextBox)
选择该组件以打开其
属性窗口。在此窗口中,修改
以下属性:
名称:txtSaisie,文本:留空
  • 按钮 3 (Button):
名称cmdAfficher,文本:显示
  • 窗口本身:名称:frmSaisies&Boutons,
文本:输入框与按钮 - 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)是表单控件列表:

Image

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

Image

与该组件关联的事件之一以粗体显示(此处为 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 事件。该事件处理程序有两个参数:

sender
触发事件的对象(在此示例中为按钮)
e
一个 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

运行应用程序时,会出现以下情况:

Image

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 关键字允许在运行时将事件与过程关联起来。这在生产者-消费者事件框架中非常有用。一个对象会触发一个特定事件,该事件可能引起其他对象的关注。这些对象会订阅该生产者以接收事件(例如,温度超过临界阈值)。在应用程序的执行过程中,事件生产者将需要执行各种指令:
Addhandler E, AddressOf P1
Addhandler E, AddressOf P2
...
Addhandler E, AddressOf Pn

其中 E 是生产者产生的事件,P1 是属于消费该事件的各个对象之一。我们将在后面的章节中再次探讨生产者-消费者事件应用程序。

5.3.4. 结论

从所研究的两个项目中,我们可以得出结论:一旦使用 VS.NET 构建了图形用户界面,开发者的任务就是为该界面中需要管理的事件编写事件处理程序。从现在起,我们将仅展示这些处理程序的代码。

5.4. 一些有用的组件

接下来我们将展示若干利用常见组件的应用程序,以探讨其主要方法和属性。对于每个应用程序,我们将展示图形用户界面以及相关代码,特别是事件处理程序。

5.4.1. 窗体

我们将首先介绍一个关键组件:用于放置其他组件的窗体。我们之前已经介绍过其部分基本属性。在此,我们将重点关注几个重要的窗体事件。

 
表单正在加载
关闭
表单正在关闭
已关闭
表单已关闭

Load 事件发生在表单显示之前。Closing 事件发生在表单关闭时。此时仍可通过编程方式阻止关闭操作。我们创建一个名为 Form1 的表单,其中不包含任何控件:

Image

我们处理前三个事件:


    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 支持多行输入,请设置以下控件属性:

 
以支持多行文本
ScrollBars=(None, Horizontal, Vertical, Both)
指定控件是否应显示滚动条(水平、垂直、两边)或不显示(无)
AcceptReturn=(True, False)
若设置为 true,按 Enter 键将跳至下一行
AcceptTab=(True, False)
如果设置为 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 类的更多信息,请在“帮助”索引(“帮助”/“索引”)中输入 ComboBoxComboBox 类有一个构造函数:

Public Sub New()
创建一个空的 ComboBox 对象

ComboBox 中的项目可通过 Items 属性访问:

Public ReadOnly Property Items As ComboBox.ObjectCollection

这是一个索引属性,其中 Items(i) 表示下拉列表中的第 i 个项目。设 C 为一个下拉列表,C.Items 为其项目列表。我们有以下属性:

 
ComboBox 中的项目数量
C.Items(i)
ComboBox 的第 i 个元素
C.Add(object o)
将对象 o 作为下拉列表的最后一个元素添加
C.AddRange(object() objects)
将一个对象数组添加到下拉列表的末尾
C.Insert(int i, object o)
将对象 o 插入下拉列表的第 i 个位置
C.RemoveAt(int i)
从组合框中移除第 i 个元素
C.Remove(object o)
从组合框中移除对象 o
C.Clear()
清除下拉列表中的所有项目
C.IndexOf(object o)
返回对象 o 在下拉列表中的位置 i

下拉列表通常包含字符串,因此它能包含对象这一事实可能令人感到惊讶。从视觉上看,确实如此。 如果 ComboBox 包含一个对象 obj,它将显示字符串 obj.ToString()。请记住,每个对象都继承了来自 Object 类的 ToString 方法,该方法返回一个“代表”该对象的字符串。下拉列表框 C 中选中的项目是 C.SelectedItemC.Items(C.SelectedIndex),其中 C.SelectedIndex 是选中项的索引,从第一个项开始计数为 0。

当从下拉列表中选中一个项目时,会触发 SelectedIndexChanged 事件,该事件可用于检测组合框选中的变化。在下面的应用程序中,我们利用此事件来显示从列表中选中的项目。

Image

此处仅展示与窗口相关的代码。在窗体的构造函数中,我们为下拉列表填充数据:


  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 组件与滑块类似:它同样具有 MinimumMaximumValue 属性,其默认值分别为 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. 鼠标事件

在容器中绘制时,了解鼠标位置非常重要,例如在点击时显示一个点。鼠标移动会在鼠标所在的容器中触发事件。

 
鼠标刚刚进入控件区域
鼠标移出
鼠标刚刚离开控件区域
MouseMove
鼠标正在控件区域内移动
MouseDown
鼠标左键被按下
鼠标松开
释放鼠标左键
拖放
用户将对象拖放到控件上
拖入
用户在拖动对象时进入控件区域
DragLeave
用户在拖动对象时离开控件区域
DragOver
用户在拖动对象时移出了控件区域

以下是一个程序,可帮助您更好地理解各种鼠标事件何时发生:

编号
类型
名称
角色
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 属性:

Image

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

Image

Image

Image

在 [InitializeComponent] 方法中,这些选项生成的代码如下:


        Me.lstEvts.Cursor = System.Windows.Forms.Cursors.Hand
        Me.btnEffacer.Cursor = System.Windows.Forms.Cursors.No

此外,为了检测鼠标在列表 2 上的进入和离开,我们处理了该列表的 MouseEnterMouseLeave 事件:


    ' 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

Image

为了处理表单上的点击事件,我们处理 MouseDownMouseUp 事件:


    ' é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

Image

最后,是“删除”按钮的 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 的只读 TextBoxReadOnly=true)。菜单树如下:

菜单选项与其他可视化组件一样,都是控件,具有属性与事件。例如,菜单选项 A1 的属性表:

Image

本示例中使用了两个属性:

 
菜单控件的名称
文本
菜单选项的标签

本示例中各个菜单选项的属性如下:

名称
文本
mnuA
选项 A
mnuA1
A1
mnuA2
A2
mnuA3
A3
mnuB
选项 B
mnuB1
B1
mnuSep1
- (分隔符)
mnuB2
B2
mnuB3
B3
mnuB31
B31
mnuB32
B32

要创建菜单,请从“工具箱”栏中选择“MainMenu”组件:

Image

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

Image

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

Image

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

Image

如果你触发上面的 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,即可看到以下消息:

Image

除了 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 的内容

使用了两个非视觉控件:

Image

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

Image

“清除”按钮的代码很简单:


    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 类的定义如下:

Image

它继承自多个类层级。在其众多属性和方法中,我们将重点关注以下内容:

string Filter
对话框中文件类型下拉列表中提供的文件类型
int FilterIndex
上述列表中默认显示的文件类型的索引。从 0 开始。
字符串
InitialDirectory
保存文件时初始显示的文件夹
字符串 FileName
用户指定的保存文件名
DialogResult.ShowDialog()
一个用于显示保存对话框的方法。返回一个 DialogResult 对象。

ShowDialog 方法会显示一个类似于以下内容的对话框:

1
Filter 属性生成的下拉列表。默认文件类型由 FilterIndex 决定
2
当前目录,若已指定 InitialDirectory 属性,则由该属性设定
3
用户所选或直接输入的文件名。该名称将可在 FileName 属性中获取
4
“保存/取消”按钮。若使用“保存”按钮,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
  • 我们将初始目录设置为包含应用程序可执行文件的目录:
        saveFileDialog1.InitialDirectory = Application.ExecutablePath
  • 我们将要显示的文件类型设置为
        saveFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*"

请注意过滤器语法:filter1|filter2|..|filteren,其中 filteri = 文本|文件类型。在此,用户可以在 *.txt 和 *.* 文件之间进行选择。

  • 我们将文件类型设置为在列表开头显示
        saveFileDialog1.FilterIndex = 0

在此,系统将首先向用户显示 *.txt 格式的文件。

  • 显示对话框并获取其结果

    If saveFileDialog1.ShowDialog() = DialogResult.OK Then
  • 在对话框显示期间,用户无法访问主窗体(即所谓的模态对话框)。用户设置待保存文件的名称,并通过单击“保存”按钮、“取消”按钮或直接关闭对话框来退出对话框。只有当用户使用“保存”按钮退出对话框时,ShowDialog 方法的结果才会是 DialogResult.OK
  • 完成上述操作后,待创建文件的名称现已存储在 saveFileDialog1 对象的 FileName 属性中。随后,我们回归到创建文本文件的标准流程。我们将 TextBox 的内容(txtTexte.Text)写入文件,同时处理可能发生的任何异常。

OpenFileDialog 类与 SaveFileDialog 类非常相似,且源自相同的类继承体系。在这些属性和方法中,我们将重点关注以下内容:

string Filter
对话框中文件类型下拉列表所提供的文件类型
int FilterIndex
上述列表中默认显示的文件类型的索引。从 0 开始。
字符串 InitialDirectory
初始显示用于搜索要打开文件的目录
字符串 FileName
用户指定的要打开的文件名
DialogResult.ShowDialog()
一个用于显示保存对话框的方法。返回一个 DialogResult 对象

ShowDialog 方法会显示一个类似于以下内容的对话框:

1
Filter 属性生成的下拉列表。默认文件类型由 FilterIndex 决定
2
当前文件夹,若已指定 InitialDirectory 属性,则由该属性设定
3
用户所选或直接输入的文件名。该名称将显示在 FileName 属性中
4
“打开”/“取消”按钮。如果使用“打开”按钮,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
  • 我们将初始目录设置为包含应用程序可执行文件的目录:
            saveFileDialog1.InitialDirectory=Application.ExecutablePath
  • 我们将要显示的文件类型设置为
            saveFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*"
  • 设置首先显示的文件类型
            saveFileDialog1.FilterIndex = 0

这样,系统会首先向用户显示 *.txt 文件。

  • 显示对话框并获取其结果

    If openFileDialog1.ShowDialog() = DialogResult.OK Then

在对话框显示期间,用户无法访问主窗体(即所谓的模态对话框)。用户指定要打开的文件名,并通过单击“打开”按钮、“取消”按钮或关闭对话框来退出对话框。只有当用户使用“打开”按钮退出对话框时,ShowDialog 方法的结果才会是 DialogResult.OK

  • 完成上述操作后,待创建文件的名称现已存储在 openFileDialog1 对象的 FileName 属性中。随后,我们回到读取文本文件的标准流程。请注意以下允许读取整个文件的方法:
                    txtTexte.Text=fichier.ReadToEnd
  • 文件内容将被放入 txtTexte 文本框中。我们会处理可能发生的任何异常。

5.7.2. FontColor 和 ColorDialog 对话框

我们延续上一个示例,引入两个新按钮:

类型
名称
角色
6
按钮
btnColor
用于设置文本框的文字颜色
7
按钮
btnFont
用于设置文本框的字体

我们在窗体上放置一个 ColorDialog 控件和一个 FontDialog 控件:

Image

FontDialogColorDialog 类都拥有一个与 OpenFileDialogSaveFileDialog 类的 ShowDialog 方法类似的 ShowDialog 方法。ColorDialog 类的 ShowDialog 方法允许您选择颜色:

Image

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

Image

如果用户通过单击“确定”按钮关闭对话框,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
每秒触发一次事件的组件

计时器正在运行:

Image

计时器已停止:

Image

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

Image

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

Image

在其属性中,我们将仅关注以下内容:

Interval
触发Tick事件所需的毫秒数。
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 对象的 HoursMinutesSeconds 属性,它们分别代表持续时间的小时、分钟和秒数。我们使用 ToString("d2") 格式进行显示,以确保以两位数形式呈现。

5.8. 税费计算示例

我们回到之前已讲解过两次的 IMPOTS 应用程序。现在,我们将为其添加一个图形用户界面:

控件如下所示

编号
类型
名称
角色
1
单选按钮
rdYes
已婚时勾选
2
单选按钮
rdNo
未婚时勾选
3
数字上下滑块
incChildren
纳税人子女的数量
最小值=0,最大值=20,增量=1
4
文本框
txtSalary
纳税人的年薪(单位:F)
5
文本框
txtTaxes
应缴税额
只读=true
6
按钮
btnCalculate
开始计算税款
7
按钮
btnClear
在加载时将表单重置为初始状态
8
按钮
btnExit
用于退出应用程序

操作规则

  • 只要薪资字段为空,“计算”按钮将保持禁用状态
  • 如果在运行计算时,发现薪资有误,系统将报告错误:

Image

程序代码如下所示。它使用了“类”一章中创建的 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 类的编译结果。请注意,该程序集可以通过以下命令在控制台模式下生成

dos>vbc /t:library impots.vb

该命令会生成名为 impots.dll 的文件,即所谓的程序集。该程序集随后可在各种项目中使用。在此,我们在 VS.NET 下的项目中,通过项目属性窗口进行操作:

Image

要添加引用(即程序集),我们右键单击上方的“引用”组,选择[添加引用]选项,并指定我们已放置在项目文件夹中的[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 文件。