UML软件工程组织

重现 Visual Basic 6.0 特色内容
Billy Hollis

2003 年 5 月 14 日

摘要:Billy Hollis 向您展示了如何在 Visual Basic .NET 中创建控件数组、Forms 集合以及能将所有控件集于一个窗体的 Controls 集合。

下载 vbnet05132003_sample.exe 示例文件

我不是很怀念 Microsoft Visual Basic® 6.0。Visual Basic .NET 似乎更符合我设计软件的方式。与 Visual Basic 6.0 提供的有限功能相比,Visual Basic .NET 提供的完全面向对象的功能让我可以灵活地处理以前无法处理的内容。更不用说它具有较好的部署、Web 接口以及去掉了离奇的 COM 细节等。

然而,在与开发人员交谈时,我听到他们遗憾地提到 Visual Basic .NET 中去掉了 Visual Basic 6.0 原有的一些内容。这是可以理解的。一旦熟悉了某些操作方式后,再要放弃这些技术,将会让人感到遗憾。

然而,对于一个灵活的、完全面向对象的环境来说,其重要之处在于通常可以通过巧妙地使用新技术来重新获得旧技术。本文将介绍三个在 Visual Basic .NET 中未包含的最显著的功能:

  1. 控件数组
  2. Forms 集合
  3. 将所有控件集于一个窗体的 Controls 集合

我将介绍如何在 Visual Basic .NET 中重现每项功能。

控件数组

在 Visual Basic 6.0 以及更早的版本中,控件数组是位于一个窗体上共享同一名称的一组同种类型控件。普通控件仅使用名称就可以识别,而引用控件数组中的成员则需要同时使用名称和索引。

一个控件数组的所有成员共享公用事件。也就是说,如果单击数组中的任一控件,Visual Basic 6.0 都会将该事件传送到一个 Click 事件处理程序。事件有一个参数是一个索引,该索引指明触发事件的控件。

开发人员在 Visual Basic 6.0 以及更早的版本中使用控件数组主要有以下三个理由:

  1. 允许一个事件例程与多个控件连接
  2. 在 For Each 循环中访问集合中的控件
  3. 向窗体快速添加新控件

上述功能都能够在 Visual Basic .NET 中轻松地完成,但需要的技术对于大多数 Visual Basic 6.0 开发人员来说看起来不太熟悉。接下来讨论上面提到的每项功能需要的相关 .NET 技术。

将控件与事件连接

在讨论控件数组和事件之前,有必要了解 Visual Basic .NET 中的事件。Visual Basic .NET 处理事件的方式与 Visual Basic 6.0 处理事件的方式有着很大的差别。刚开始您或许没注意到这些变化。在代码编辑器中创建事件的方式看上去一样。

如果一个对象与事件相连接(因为已经使用关键字 WithEvents 进行声明),则在 Visual Basic .NET 中该对象出现在代码编辑器的左侧下拉菜单中。如果在列表中突出显示该对象,则其事件出现在右侧下拉菜单中。如果接着选择该下拉菜单中的事件,您将获得事件例程外壳。

只有查看该外壳例程中的代码时,才会看出 Visual Basic 6.0 与 Visual Basic .NET 中事件的差异。在 Visual Basic 6.0 中,控件或类实例的事件例程具有标准的名称,由对象名称、下划线和事件名称组成。因此,名为 txtAge 的文本框的 Click 事件是 txtAge_Click。如下所示:

' VB6 中的 Click 事件
Private Sub txtAge_Click()

End Sub

Visual Basic .NET 也为 txtAge 创建一个名为 txtAge_ClickClick 事件。然而,以该名称命名只是为了增加代码的可读性。实际上,您可以按照自己的意愿命名事件例程。

以下是 Visual Basic .NET 创建的相应 Click 事件(为了增加代码的可读性稍微作了修改):

' VB.NET 中的 Click 事件
Private Sub txtAge_Click(ByVal sender As Object, _
                         ByVal e As System.EventArgs) _
                         Handles txtAge.Click

End Sub

就这个事件例程而言,不是使用名称将例程连接到事件。您可以将例程从 txtAge_Click 重新命名为 MyFunkyEventRoutine,这不会产生任何影响。

而是由位于例程声明末尾的 Handles 子句将例程连接到事件。在本例中,连接的事件是 txtAge 文本框的 Click 事件。只要末尾出现 Handles txtAge.Click,无论使用什么例程名称,单击文本框时该事件例程都将启动。

还有其他一些有趣的可能情况。首先,您可以使用多个末尾处具有 Handles txtAge.Click 的例程。如果单击 txtAge 文本框,所有具有 Handles txtAge.Click 的例程都会启动。(此外,您无法控制事件例程启动的顺序,因此不要寄希望于事件例程能按特定的顺序启动。本文后面会再次谈到这个问题。)

最后,我们介绍用于将多个控件指向同一事件的技术。位于事件例程末尾的 Handles 子句可以按您的需要列出多个事件,并以逗号分隔。下面对上述事件例程稍作修改,该例程将启动由三个不同的文本框生成的 Click 事件:

Private Sub MultiTextBox_Click(ByVal sender As Object, _
                   ByVal e As System.EventArgs) _
                   Handles txtAge.Click, txtHeight.Click, txtWeight.Click

End Sub

请注意,已将例程从 txtAge_Click 重命名为 MultiTextBox_Click。这种做法很好,不然阅读代码的读者不会注意到例程是与 txtAge 以外的文本框一起使用的。

在 Visual Basic .NET 中使用此语法,您可以将多个控件连接到同一事件,而无需使用控件数组。因为可以与任一控件的 Click 事件一起使用,此语法具有更大的灵活性。事件例程不局限于仅处理一种类型的控件。可以使用一个事件例程捕获来自一组不同类型的控件的 Click 事件。如果您具有名为 chkHasCarchkCertified 的复选框,可以修改上述例程,以捕获来自文本框和复选框的事件:

Private Sub MultiControl_Click(ByVal sender As Object, _
                 ByVal e As System.EventArgs) _
                 Handles txtAge.Click, txtHeight.Click, txtWeight.Click, _
                 chkHasCar.Click, chkCertified.Click

End Sub

实际上,除 Click 事件以外,只要其他事件具有完全相同的参数列表,例程也能捕获该事件。例如,EnterDoubleClick 事件与 Click 事件具有相同的参数列表。因此,如果将上述例程改写成如下所示,则可以捕获文本框和复选框的所有事件:

Private Sub CatchMultipleEvents(ByVal sender As Object, _
                 ByVal e As System.EventArgs) _
                 Handles txtAge.Click, txtAge.DoubleClick, _
                 txtAge.Enter, chkHasCar.Click, _
                 chkHasCar.DoubleClick, chkHasCar.Enter

End Sub

由此可见,不但没有因为未使用控件数组而丧失相应的功能,而且与 Visual Basic 6.0 相比,我们在事件处理时实际获得了更大的灵活性。

在 For Each 循环中访问控件

由于 Visual Basic .NET 不支持控件数组,因此,如果要循环一组控件,控件必须是集合的元素。最直接的办法就是创建集合并添加相关的控件。例如,假定我们拥有一个具有三个复选框的窗体,三个复选框的名称分别为 CheckBox1CheckBox2CheckBox3。我们可以在窗体中声明一个 ArrayList 集合并将复选框添加到其中,如下所示:

Dim colMyCheckBoxes As ArrayList
Private Sub BuildCheckBoxCollection()
    colMyCheckBoxes = New ArrayList
    colMyCheckBoxes.Add(CheckBox1)
    colMyCheckBoxes.Add(CheckBox2)
    colMyCheckBoxes.Add(CheckBox3)
End Sub

通常,我们需要在窗体的 Load 事件中运行该例程。此类 Load 事件如下所示:

Private Sub Form1_Load(ByVal sender As Object, _
                       ByVal e As System.EventArgs) _
                       Handles MyBase.Load
    BuildCheckBoxCollection()
End Sub

现在可以轻松地遍历复选框来寻找影响复选框的任何操作。例如,要查看处于选中状态的复选框的数量,可以在窗体上的某个按钮背后置入以下代码:

Dim obj As Object
Dim iCount As Integer = 0
For Each obj In colMyCheckBoxes
    If TypeOf obj Is CheckBox Then
        Dim chkCheckBox As CheckBox
        chkCheckBox = CType(obj, CheckBox)
        If chkCheckBox.Checked Then
            iCount += 1
        End If
    End If
Next
MsgBox(iCount.ToString & " boxes checked")

而且您可以走捷径,而无需创建和管理集合。如果您将希望包含在某个集合中的控件放在一个面板中,则使用面板的 Controls 属性可以自动获得包含所有控件的一个集合。

让我们看一下实际过程。假定我们向窗体添加一个名为 Panel1 的面板,然后将三个复选框移入该面板。现在可以去掉用来创建 colMyCheckBoxes 集合的代码,按钮背后的代码如下所示:

Dim ctl As Control
Dim iCount As Integer = 0
For Each ctl In Panel1.Controls
    If TypeOf ctl Is CheckBox Then
        Dim chkCheckBox As CheckBox
        chkCheckBox = CType(ctl, CheckBox)
        If chkCheckBox.Checked Then
            iCount += 1
        End If
    End If
Next
MsgBox(iCount.ToString & " boxes checked")

这项技术的优点是集合仅包含控件。这意味着某些操作不需要使用 Ctype 来将集合元素强制转化成某种特定类型。例如,如果我们要将集合中的所有控件的字体变成粗体,代码如下所示:

Dim ctl As Control
For Each ctl In Panel1.Controls
    Dim fnt As New Font(ctl.Font, FontStyle.Bold)
    ctl.Font = fnt
Next

由于所有控件共享包含 Font 属性的多态接口,因此使用上述代码可以使集合中任一控件的字体变成粗体。

将两项功能结合起来

以上介绍的在多个控件中共享同一事件和将多个控件放在一个集合中这两项技术单独使用时都可以正常工作,却没有办法将两者捆绑在一起,并且每次新的控件需要共享事件时,用于事件的第一种技术要求编写代码。

但可以使用 Visual Basic .NET 中的一些高级事件功能来建立综合上述两种功能的一种方法。可以创建一个特别的面板,将事件动态地连接起来。这样,无需在窗体中编写代码来管理控件数组,就可以获得近似控件数组的功能。

使用“继承”创建一个作为新类型控件的新面板。我以前介绍过有关在 Visual Basic .NET 中创建控件方面的内容(请参阅从头创建 Visual Basic .NET 控件),在此就不详细讨论了。仅介绍操作步骤,并附带介绍如何管理事件。

我们希望连接事件,以便当面板中的控件启动事件时,可以将事件集合到一个公用的例程中,然后在支撑该面板的窗体中突出显示该事件。要达到此目的非常简单,步骤如下:

  1. 在 Visual Studio® .NET 中创建一个新的 Windows 控件库项目,并将其命名为 ControlPanelArray
  2. 将解决方案管理器中的 UserControl1.vb 文件名改为 ControlPanelArray.vb
  3. 转到 ControlPanelArray.vb 的代码。头几行应该是:
    Public Class UserControl1
        Inherits System.Windows.Forms.UserControl
    Change them to look like this:
    Public Class ControlPanelArray
        Inherits System.Windows.Forms.Panel
    

    此操作赋予新类型面板一个类名 (ControlPanelArray),并指定它是从基类 Windows Forms Panel 控件继承来的。

  4. 只要事件是基类 Control 类型(所有控件都是从该类派生得到的)接口的一部分,则可以安排新的 ControlPanelArray 来根据需要处理多个不同类型的事件。此例中用 ControlPanelArray 管理面板中每个控件的如下事件:
    • Click
    • KeyPress
    • MouseUp

    对于每种事件,我们希望在 ControlArrayPanel 中捕获由控件生成的事件并连接事件。然后,在包含 ControlArrayPanel 的窗体中突出显示连接的事件。这意味着 ControlArrayPanel 需要声明将在使用 ControlArrayPanel 的窗体中突出显示的事件。可以进行如下事件声明:

    Public Event InternalControlClick(ByVal sender As Object, _
                                      ByVal e As EventArgs)
    Public Event InternalControlKeyPress(ByVal sender As Object, _
                                         ByVal e As KeyPressEventArgs)
    Public Event InternalControlMouseUp(ByVal sender As Object, _
                                        ByVal e As MouseEventArgs)
    

    此代码应直接置于标有 Windows Forms Designer Generated Code 的代码区域下面。

  5. 要捕获 ControlArrayPanel 中控件的各个事件,我们需要一些动态连接的事件处理程序。每种要捕获的事件都需要一个事件处理程序。以下是适用于步骤 4 中所列事件的三种事件处理程序的代码:
    Private Sub InternalClickHandler(ByVal sender As Object, _
                                     ByVal e As EventArgs)
        RaiseEvent InternalControlClick(sender, e)
    End Sub
    
    Private Sub InternalKeyPressHandler(ByVal sender As Object, _
                                        ByVal e As KeyPressEventArgs)
        RaiseEvent InternalControlKeyPress(sender, e)
    End Sub
    
    Private Sub InternalMouseUpHandler(ByVal sender As Object, _
                                       ByVal e As MouseEventArgs)
        RaiseEvent internalControlMouseUp(sender, e)
    End Sub
    

    这些事件是 ControlArrayPanel 的“内部事件”,仅在使用窗体中突出显示相应的已连接的事件,而连接的事件在 ControlArrayPanel 的“外部”启动。上述代码应置于步骤 4 中事件声明的下面。

  6. 当向面板添加控件时,我们需要连接这些事件处理程序。此操作应在 ControlArrayPanelControlAdded 事件中完成。以下是 ControlAdded 事件的完整代码,此代码紧跟在步骤 5 中添加的代码的下面显示:
    Private Sub ControlArrayPanel_ControlAdded(ByVal sender As Object, _
                      ByVal e As System.Windows.Forms.ControlEventArgs) _
                      Handles MyBase.ControlAdded
        AddHandler e.Control.Click, AddressOf InternalClickHandler
        AddHandler e.Control.KeyPress, AddressOf InternalKeyPressHandler
        AddHandler e.Control.MouseUp, AddressOf InternalMouseUpHandler
    End Sub
    

    您或许不熟悉 AddHandler 关键字,它将动态地将控件和组件突出显示的事件连接到能够处理事件的特定子例程。它相当于本文前面讨论过的 Handles 子句,同样可以将特定控件的事件连接到能够处理该事件的例程。

    要连接的子例程必须具备接受该事件所需的参数列表。例如,例程 InternalClickHandler 具有两个参数:sendere。前者为 Object 类型,而后者为 EventArgs 类型。此例程可以连接到 Click 事件,因为 Click 事件的参数列表具有相同的参数。(也可以连接到 DoubleClickEnter 事件,因为从前面可以看到它们具有同样的参数列表。)

    如果试图使用 AddHandlere.Control.KeyPress 连接到 InternalClickHandler,将会出现语法错误提示:“方法不具有委派的签名”,其中,省略部分由方法描述和事件参数列表(有时也称为“方法签名”)所替代。(试一试,以查看详细的错误消息。)

    由于 ControlAdded 事件中放置了此逻辑,因此 ControlArrayPanel 中添加的每个控件的 Click 事件、KeyPress 事件和 MouseUp 事件都被捕获,并被传送到相应的事件连接例程。如前面所述,这些例程在使用的窗体中突出显示该事件。

  7. 要使 ControlArrayPanel 稳定可靠,当从 ControlArrayPanel 删除控件时需要删除处理程序。为此,请创建以下代码所示的 ControlRemoved 事件,并将代码紧跟在步骤 6 中添加的 ControlAdded 事件的下面放置:
    Private Sub ControlArrayPanel_ControlRemoved(ByVal sender As Object, _
                      ByVal e As System.Windows.Forms.ControlEventArgs) _
                      Handles MyBase.ControlRemoved
        RemoveHandler e.Control.Click, AddressOf InternalClickHandler
        RemoveHandler e.Control.KeyPress, AddressOf InternalKeyPressHandler
        RemoveHandler e.Control.MouseUp, AddressOf InternalMouseUpHandler
    End Sub
    

    RemoveHandler 负责控件的事件与处理该事件的 ControlArrayPanel 内部例程之间的连接。RemoveHandler 的语法与 AddHandler 的语法相同。

  8. 至此,我们获得了一个完整的控件。生成控件以确保无误。
  9. 要测试控件,请选择 File | Add Project | New Project(文件 | 添加项目 | 新项目),并在 New Project (新项目)对话框中选择 Windows Application(Windows 应用程序)项目类型。将新项目命名为 TestControlArrayPanel,并按 OK(确定)。
  10. 刚添加的测试项目需要在选择 Debug | Start(调试 | 开始)或单击工具栏中的 Start(开始)按钮后可以启动。在解决方案(解决方案资源管理器中的第一个条目)上单击右键,并选择 Properties(属性)。在 Properties(属性)对话框中,选择 Single Startup Project(单启动项目)下拉菜单下的 TestControlArrayPanel
  11. 要以拖放模式使用 ControlArrayPanel 控件,需要将其添加到工具箱。在工具箱中的 Windows Forms(Windows 窗体)选项卡上单击右键,并选择 Visual Studio .NET 2002 中的 Customize Toolbox(自定义工具箱),或者选择 Visual Studio .NET 2003 中的 Add/Remove Items(添加/删除项)。浏览到 ControlArrayPanel 项目,然后浏览到 bin 目录。它包含一个名为 ControlArrayPanel.dll 的 DLL。选择该 DLL,并选择 Open(打开)。然后单击 OK(确定)。工具箱底部将出现 ControlArrayPanel 的图标。
  12. ControlArrayPanel 控件拖到 TestControlArrayPanel 中的空白 Form1 上。该控件将被命名为 ControlArrayPanel1。然后将三个单选按钮拖到 ControlArrayPanel 中,并设置按钮的名称和文本属性,如下所示:
    属性名称 设置文本为:
    btnRed 红色
    btnYellow 黄色
    btnLightGreen 淡绿色
  13. 将一个按钮拖到 ControlArrayPanel 中。将其名称设置为 btnRestore 并将其文本属性设置成 Restore Color
  14. 转到 Form1 的代码窗口。在代码编辑器上端的左下拉菜单中,选择 ControlArrayPanel1。然后在右下拉菜单中选择 InternalControlClick 事件。在事件中,置入以下代码:
    Dim ctlSender As Control
    ctlSender = CType(sender, Control)
    Select Case ctlSender.Name.ToUpper
        Case "optRed".ToUpper
            ControlArrayPanel1.BackColor = Color.Red
        Case "optYellow".ToUpper
            ControlArrayPanel1.BackColor = Color.Yellow
        Case "optLightGreen".ToUpper
            ControlArrayPanel1.BackColor = Color.LightGreen
        Case "btnRestore".ToUpper
            ControlArrayPanel1.BackColor = SystemColors.Control
    End Select
    

    当单击 ControlArrayPanel 中的任一控件时,将会启动该事件。参数 sender 标识 ControlArrayPanel 中被单击的控件。由于控件属于 Object 类型,因此访问该控件之前,必须将其转换成 Control,代码中的头两行就是完成此项操作。然后可以正常操纵该控件。

  15. 最后,回到窗体的设计视图中,并双击 ControlArrayPanel 中的按钮以便为该按钮创建 Click 事件。在按钮的该事件中置入以下代码:
    MsgBox("Restore color button pressed")
    Dim ctl As Control
    For Each ctl In ControlArrayPanel1.Controls
        If TypeOf (ctl) Is RadioButton Then
            Dim ctlRadioButton As RadioButton
            ctlRadioButton = CType(ctl, RadioButton)
            ctlRadioButton.Checked = False
        End If
    Next
    

    该代码说明可以像在普通面板中一样遍历 ControlArrayPanel 中的控件。因此,我们将事件连接与访问集合中的多个控件两项技术组合成一个类 ControlArrayPanel

  16. 运行并测试应用程序。如果单击标签文本为“黄色”的单选按钮,将获得如图 1 所示的结果。请注意单击该按钮后,窗体中的两个事件例程均会启动。

    当一个事件连接到多个处理程序时,请不要寄希望于事件处理程序按特定的顺序启动。您应该使每个处理程序的逻辑完全独立于任何其他处理程序的逻辑。

    图 1:位于一个 ControlArrayPanel 中的三个单选按钮和一个标准按钮。已选中“黄色”单选按钮,该单选按钮的 Click 事件被指向 Form1 中连接的事件处理程序。

向窗体快速添加控件

要向 Visual Basic 6.0 窗体动态地添加控件,该控件必须是控件数组的元素。这是一个明显的限制,因为意味着“种子”控件必须已经在窗体中存在时才可以创建控件数组。

所有关于在 Visual Basic 6.0 中动态地将控件置入窗体方面的限制在“Windows 窗体”中已不复存在。从某种意义上说,“全部”控件动态地放置在窗体上,因为是用可视化设计器来建立代码以创建控件并设置其属性。您可以使用同样的语法随时将控件放置在窗体上。

要在窗体上放置名为 txtExtraStuff 的文本框,可以使用如下所示的逻辑:

Dim txtExtraStuff As New TextBox
txtExtraStuff.Top = 40
txtExtraStuff.Left = 180
txtExtraStuff.Text = "Some stuff"
txtExtraStuff.Width = 130
Me.Controls.Add(txtExtraStuff)

可以像其他类一样实例化新的控件,然后设置相关属性。未设置的属性均采用默认值。

最后一行使控件在窗体上可见。只要将控件添加到窗体的 Controls 集合,就会使窗体在重画自己的外观时包含该控件。

添加控件后,需要将控件连接到事件处理程序。使用前面示例中的 AddHandler 语法可以实现这个目的。只需为要捕获的新控件的每种事件加入 AddHandler 行即可。当然,需要事先对事件处理程序例程进行编码。

快速添加控件这项技术非常灵活和强大,一旦使用它,您就会明白过去使用控件数组向窗体添加控件是多么的平淡无奇。

获取 Forms 集合

Visual Basic 6.0 开发人员常常喜欢在一个应用程序中遍历当前加载的窗体以查找有关信息,例如是否加载了某一特定窗体,或加载了多少个特定类型的窗体。但 Windows 窗体不包含 Forms 集合,因此在 Visual Basic .NET 中获得此功能就需要进行额外的工作。

创建自己的集合以包含已加载的窗体是一件非常简单的事,就像本文前面创建控件集合一样。问题在于如何从应用程序访问集合,以及如何在整个应用程序过程中不断地添加窗体。

解决这两种要求的好方法就是在应用程序中将集合创建成某个类的共享属性。该类也可以通过共享的方法来显示窗体,而该方法可以确保窗体在显示以前已经存在于集合中。

要创建这样的一个类,请在 Visual Basic .NET 中启动一个新的 Windows 应用程序。选择 Project | Add Class(项目 | 添加类)。将新添加的类命名为 MyForms。用以下代码替换新类中的代码:

Public Class MyForms

    Private Shared m_colFormsCollection As New ArrayList
    Public Shared Property Forms() As ArrayList
        Get
            Return m_colFormsCollection
        End Get
        Set(ByVal Value As ArrayList)
            m_colFormsCollection = Value
        End Set
    End Property

    Public Shared Sub Show(ByVal frm As Form)
        Dim obj As Object
        Dim bFound As Boolean = False
        For Each obj In m_colFormsCollection
            Dim frmCurrent As Form
            frmCurrent = CType(obj, Form)
            If frm Is frmCurrent Then
                bFound = True
                Exit For
            End If
        Next
        If Not bFound Then
            m_colFormsCollection.Add(frm)
        End If
        frm.Show()

    End Sub
End Class

现在向 Windows 应用程序添加另一个窗体。将其命名为 Form2。转到 Form1 的设计界面,并拖动两个按钮。在 Button1 的 Click 事件中,置入以下代码:

Dim f As New Form2
MyForms.Show(f)

在 Button2 的 Click 事件中,置入以下代码:

Dim obj As Form
For Each obj In MyForms.Forms
    Dim frm As Form = CType(obj, Form)
    frm.Visible = False
Next

运行并测试该程序。单击第一个按钮数次以获得 Form2 的一些实例。然后单击第二个按钮,将显示 Form2 的全部实例。

许多 Visual Basic 6.0 开发人员也许会使用全局变量(例如 Forms 集合的全局变量)来解决这个问题。但使用上面的具有共享成员的一个类会更好一些。像全局变量一样,项目中任何位置都可以访问该类,而且该类还具有可以更智能地管理集合的逻辑。例如,上面的 Show 方法确保不会向集合添加重复的窗体。

Controls 集合的差异

在 Visual Basic 6.0 中,窗体的 Controls 集合返回窗体上的所有控件,无论它们是否已包含在窗体的另一个控件(例如框架)中。为了证实这一点,可以创建一个 Visual Basic 6.0 窗体,其中包含一个框架,框架内部放置有两个按钮,外部放置有一个按钮,如图 2 所示。

图 2:用于测试 Visual Basic 6.0 中 Controls 集合的 Visual Basic 6.0 窗体

在 Visual Basic 6.0 中 Command3 的 Click 事件背后置入以下代码:

Dim ctl As Control
For Each ctl In Me.Controls
    Debug.Print ctl.Caption
Next ctl

运行该程序并单击 Command3,调试窗口中将显示以下四行:

Command3
Frame1
Command2
Command1

如果在 Visual Basic .NET 执行同样的操作,会得到不一样的结果。在 Visual Basic .NET 中创建一个类似的窗体,其中含有一个 GroupBox 和三个按钮,如图 3 所示。

图 3:用于测试 Visual Basic .NET 中 Controls 集合的窗体

在 Button3 的 Click 事件中置入以下逻辑:

Dim ctl As Control
For Each ctl In Me.Controls
    Console.WriteLine(ctl.Text)
Next

当运行该程序并单击 Button3 时,“输出”窗口中将只给出两行输出。

Button3
GroupBox1

不会得到 GroupBox 中的两个按钮的任何输出,因为这两个按钮不属于 Form1 的 Controls 集合。相反,它们属于 GroupBox1 的 Controls 集合。

在 Windows 窗体中,控件可以具有任意级别,被其他控件包含的控件还可以包含控件。那么如何获得窗体上的所有控件?

这是一个需要使用递归的典型例子。我们可以创建一个函数,该函数会对各个容器级别进行递归,直到最后一级,并集合在各级别中找到的所有控件。

向上述 Visual Basic .NET 窗体添加以下两个函数:

Private Function AllControls(ByVal frm As Form) As ArrayList
    Dim colControls As New ArrayList
    AddContainerControls(frm, colControls)
    Return colControls
End Function

Private Sub AddContainerControls(ByVal ctlContainer As Control, _
                                 ByVal colControls As ArrayList)
    Dim ctl As Control
    For Each ctl In ctlContainer.Controls
        colControls.Add(ctl)
        AddContainerControls(ctl, colControls)
    Next
End Sub

第一个函数设立 ArrayList,用于包含所有控件;第二个函数向 ArrayList 中添加控件,递归检查每个控件是否包含其他控件。

现在向窗体添加名为 Button4 的新按钮,并在其背面置入以下逻辑:

Dim ctl As Control
For Each ctl In AllControls(Me)
    Console.WriteLine(ctl.Text)
Next

运行该窗体和单击按钮后,将显示窗体上每个控件的输出行,无论其中包括什么内容。例如,假定将更多的控件拖到窗体上,其中包括一个 GroupBox,GroupBox 内部包含一个面板,而面板中又包含两个复选框。最终结果如图 4 所示。

图 4:用于测试 Visual Basic .NET 中的新 AllControls 集合的窗体

运行该程序并单击 Button4,输出文本如下所示:

Button4
Button3
GroupBox1

CheckBox2
CheckBox1
Button2
Button1

请注意空白行。该行对应于 GroupBox 中包含的面板。在默认情况下,面板控件具有空白文本属性。

小结

从 Visual Basic 6.0 转到 Visual Basic .NET 的过程变化有点大。作为完全面向对象的开发环境,Visual Basic .NET 去掉了一些 Visual Basic 6.0 特性,例如控件数组,为的是增强对象功能。开发时不使用这些功能会让人感到遗憾,但我们已经发现几乎在所有情况下功能依然存在,只是访问的方式不同。而 Visual Basic .NET 中的新功能赋予用户更大的灵活性和更好的效果,表明转换确实是一个非常有益的过程。

 


Billy Hollis 与 Rocky Lhotka 共同编写的《VB.NET Programming with the Public Beta》是第一本关于 Visual Basic .NET 的书,该书彻底地论述了主要行业会议。在 2001 年,他曾是 Microsoft 的 MSDN 区域董事,现在是 Microsoft 的 INETA 联络处的成员。他具有自己的 .NET 咨询业务,专门从事商业软件和智能客户端的开发。他还在全美国范围内提供 Visual Basic.NET 培训。

 

 

版权所有:UML软件工程组织