一前言 在微软 Visual Basic 中一条简单的 FormShow 语句就能显示项目中的第二窗体 (Form)然而它在 Visaul Basic NET 中却行不通了因为 NET 版在窗体处理机制上有了很大的变化刚刚转向 NET 版的 Visaul Basic 程序员实在难以接受这么大的变化因为现在连显示第二窗体这么简单的任务都无从下手我希望能够通过本文向大家介绍 Visaul Basic NET 与早期的 Visual Basic 在窗体处理机制上有哪些不同之处以及如何按照 NET 的模式进行多窗体编程
二Visual Basic 对 Visual Basic NET
窗体(窗体类)正如其它类一样无论在哪个版本的 Visual Basic 中都是必不可少的窗体也有属性方法和事件且在同一个项目中也允许创建多个窗体实例 (参见 http://msdnmicrosoftcom/library/enus/off/html/defInstanceasp)例如假设你在 Visual Basic 项目中定义了一个窗体 Form 则你可以创建它的 个实例并同时显示出来代码如下
Dim myFirstForm As Form
Dim mySecondForm As Form
Dim myThirdForm As Form
Set myFirstForm = New Form
Set mySecondForm = New Form
Set myThirdForm = New Form
myFirstFormShow
mySecondFormShow
myThirdFormShow
以上代码用 条 Set 语句生成了 个 Form 实例你可以把它原封不动地搬到 Visual Basic NET 中运行它照样能够正确显示 个 Form 窗体在这里Form 其实相当于一个普通的类Visual Basic 允许代码直接访问尚未实例化的窗体类然而Visual Basic NET 却规定在访问任何类之前都要进行实例化而且必须借助实例来访问类这种变化当然有可能造成许多疑惑Visual Basic 等早期版本能自动生成每个窗体的默认实例从而允许直接通过窗体名称来访问窗体例如在Visual Basic 项目中可以直接用代码FormShow 显示 Form 的默认实例然而在 Visual Basic NET 中这么做只会引发错误因为 Visual Basic NET 既不会创建默认的窗体实例也不允许直接访问尚未实例化的窗体类
这就是 Visual Basic NET 与早期 Visual Basic 在窗体处理机制上的关键区别——你只有先创建窗体实例然后才可以显示窗体外观访问窗体属性及其控件它们还有另一个区别Visual Basic 项目自动创建的默认窗体实例都能被当成全局变量使用也就是说项目中的任何代码都能直接引用窗体并且每次被引用的都是该窗体的同一个实例例如你可以在窗体中 button 控件的 Click 事件处理程序里用代码 FormShow 显示 Form 窗体然后用下列代码改变 Form 中某个 textbox 控件 (TextBox)的内容
FormTextBoxText = Fred
可是你在 Visual Basic NET 中运行它却会得到一条错误消息Reference to a NonShared Member Requires an Object Reference(引用非共享类成员必须使用对象指针)这是在提醒你你正在访问的类尚未进行实例化有一个简便的解决方案当你在调试过程中得到上述错误消息时就把相应的语句
FormShow()
改成
Dim myForm As New Form()
myFormShow()
此方案适用于大多数场合然而当项目中还有其它代码访问同一个 Form 实例 (比如改变其中 TextBox 的文本) 时你可能会考虑把下列语句
FormTextBoxText = Fred
改成
Dim myForm As New Form()
myFormTextBoxText = Fred
不幸的是这段代码创建了一个新的 Form 实例结果你所访问的窗体不再是原先的 Form 这岂不麻烦了!更坏的是你不会因此而得到任何错误消息提示同时你先前调用 Show() 显示的 Form 窗体也不会发生任何变化
三升级向导如何解决它
如果你用升级向导 (Upgrade Wizard) 把 Visual Basic 项目升级为 Visual Basic NET 版则它会在每个窗体中自动添加一段特殊代码通过显式创建窗体实例来模拟早期 Visual Basic 版本中的默认实例化机制此段代码被包裹于标号为 Upgrade Support的代码区块内借助一个新增的 Shared 属性来生成当前窗体的实例
Private Shared m_vbFormDefInstance As Form
Private Shared m_InitializingDefInstance As Boolean
Public Shared Property DefInstance() As Form
Get
If m_vbFormDefInstance Is Nothing _
OrElse m_vbFormDefInstanceIsDisposed Then
m_InitializingDefInstance = True
m_vbFormDefInstance = New Form()
m_InitializingDefInstance = False
End If
DefInstance = m_vbFormDefInstance
End Get
Set(ByVal Value As Form)
m_vbFormDefInstance = Value
End Set
End Property
代码中的 DefInstance 是一个 Shared 属性它能以 窗体名DefInstance 的形式直接访问它所在项目中的任何代码访问它都将得到同一个窗体实例这样你就能模拟 Visual Basic 项目对窗体的直接引用了只不过在代码中以FormDefInstance代替Form 而已
这时你只需用 FormDefInstanceShow() 和FormDefInstanceTextBoxText = Fred 分别替换原先对 Form 相应的直接引用就大功告成了假如你不用升级向导而是在 Visual Basic NET 窗体中手工插入上述代码 (以及升级向导在窗体的 New过程中自动添加的代码)也行当然了你并不一定非要修改窗体代码因为有一种编程模式可以在 NET 项目中模拟默认窗体实例的创建本文将用余下的篇幅来介绍这种编程模式
四NET 窗体之间的交互
在 Visual Basic 等早期版本中多个窗体之间的交互通常需要借助默认窗体实例来完成下面我将结合某些具体的编程任务来讲解如何在 NET 下实现多窗体交互希望它能对你的开发任务有所帮助
1保持窗体引用的全局性
前面提到进行 NET 窗体编程时应该牢牢把握下列原则在访问窗体之前你必须进行窗体实例化如果在项目中有多处代码访问同一窗体则你必须把它的同一实例指针传递给这些代码对于早已习惯了直接把默认窗体实例当成全局变量来使用的 Visual Basic 程序员来说这可是个严重的挑战好在 NET 为你提供了两条出路其一把窗体实例指针保存在全局变量中其二把窗体实例指针传递给任何需要访问它的窗体类模块或者过程
2NET 中的数值全局化
我以前曾经指出Visual Basic NET 不支持全局变量现在我又要说在 NET 中可以在某种程度上实现数值全局化这算不算此一时彼一时?不我不是那种人Visual Basic NET 确实不支持全局变量然而它借助 Shared (相当于 C# 中的 static) 变量却能模拟全局变量事实上前面介绍的 Visual Basic 升级向导自动添加到窗体代码中的 DefInstance 属性就是 Shared 类成员无论容纳 DefInstance 属性的窗体类是否已经实例化它都能被项目中的任何代码所引用象这样的 Shared 属性不就相当于全局变量吗?因此你可以创建这样的类
Public Class myForms
Private Shared m_CustomerForm As CustomerForm
Public Shared Property CustomerForm() As CustomerForm
Get
Return m_CustomerForm
End Get
Set(ByVal Value As CustomerForm)
m_CustomerForm = Value
End Set
End Property
End Class
你需要在首次实例化一个窗体时把该窗体的实例保存到一个类中
Dim myNewCust As New CustomerForm()
myNewCustShow()
myFormsCustomerForm = myNewCust
这里的 CustomerForm 属性值就是你的窗体实例于是其它代码就能从项目的任何地方通过它来间接访问你的窗体了
Module DoingStuffWithForms
Sub DoExcitingThings()
myFormsCustomerFormText = _
DateTimeNow()ToLongTimeString
End Sub
End Module
象这样把窗体实例保存为属性值就能按照你的要求模拟 Visual Basic 中的全局变量如此模拟的全局变量其作用域比类域 (class scope) 高一个层次所谓类域是指变量仅仅在定义它的类(确切地说应该包括模块类或窗体)中有效比类域还低一层次的是过程域 (procedure scope)即变量仅仅在定义它的例程中有效
3窗体指针在项目中的传递
除了把窗体实例全局化以外你还可以把窗体类指针保存在变量中传递给需要访问该窗体的例程假设你有一个窗体 Form并希望在点击 Form 中某个按钮 (Button) 时打开另第二窗体 Form 然后在点击第二窗体 Form 中的另一个按钮 (Button) 时进行某项计算你可以把整个代码都写在 Form 中即
Public Class Form
Inherits SystemWindowsFormsForm
Dim myForm As Form
Private Sub Button_Click(ByVal sender As SystemObject _
ByVal e As SystemEventArgs) Handles ButtonClick
myForm = New Form()
myFormShow()
End Sub
Private Sub Button_Click(ByVal sender As SystemObject _
ByVal e As SystemEventArgs) Handles ButtonClick
CalculationsCompoundInterestCalc(myForm)
End Sub
End Class
无论是把窗体指针全局化还是把它以参数的形式传递都是可行的然而你必须根据项目的需要选择最佳方案当 NET 项目中只有少数几个过程需要访问特定窗体时我建议你给这些过程增加一个参数以在必要时接受窗体指针当你的项目有太多过程需要访问该窗体时你就应该考虑设置一个全局窗体指针变量当然了你最好还是考虑调整项目代码结构使得真正访问该窗体的类或者过程只有一个如果你希望用窗体来显示登录信息则你可以先创建一个类把窗体实例保存为它的 Shared 类成员然后添加一个 Shared 方法 WriteToLogWindow 来完成实际的窗体访问于是项目中的任何代码只需调用此 WriteToLogWindow 方法就能间接访问显示登录信息的窗体了
Public Class Log
Private Shared m_LogForm As Form
Public Shared Property LogForm() As Form
Get
Return m_LogForm
End Get
Set(ByVal Value As Form)
m_LogForm = Value
End Set
End Property
Public Shared Sub WriteToLogWindow(ByVal Message As String)
Dim sb As New _
StringBuilder(m_LogFormtxtLogInfoText)
sbAppend(EnvironmentNewLine)
sbAppend(Message)
m_LogFormtxtLogInfoText = sbToString()
End Sub
End Class
4读取和改变窗体内的信息
到现在为止我们讨论的只是如何创建和访问窗体实例而没有涉及如何读取或改变窗体内的信息如果你的窗体已经按照前述方法实例化并且访问窗体的代码都位于窗体所在的项目中则你可以直接操作窗体中的任何控件来读取和改变窗体内的信息但我觉得这样并不理想与其直接访问窗体中的文本框按钮等控件还不如增加一个 Public 属性通过它来控制窗体中的控件如果你有意尝试这种特殊的窗体访问方式请跟我来
(1)在 Visual Basic NET 中新建一个 Windows 应用程序项目 此时项目中已经自动生成了一个窗体 Form
(2)现在添加另一个窗体 Form 在解决方案资源管理器中按右键单击项目名称 > 添加 > 添加 Windows 窗体 > 点击打开以接受默认名称 Formvb
(3)在 Form 中添加两个按钮分别按照默认值命名为 Button 和 Button 并且调整它们在窗体中的位置以免重叠
(4)在 Form 中添加一个简单文本框按照默认值命名为 TextBox
把下列代码添加到 Form 的End Class前面 (在解决方案资源管理器中按右键单击 Form> 查看代码再粘贴下列代码)
Public Property CustomerName() As String
Get
Return TextBoxText
End Get
Set(ByVal Value As String)
TextBoxText = Value
End Set
End Property
接下来要做的是
a 切换到 Form 的代码在 Inherits SystemWindowsFormsForm 后面增加一行
Dim myForm As New Form()
b 在 Form 中双击Button 按钮在它的 Click 事件处理程序代码中输入下列代码
myFormCustomerName = Fred
myFormShow()
c 在 Form 中双击Button 按钮在它的 Click 事件处理程序代码中输入下列代码
MessageBoxShow(myFormCustomerName)
myFormCustomerName = Joe
d 按 F 运行项目并点击窗体中的 Button 和 Button 按钮以观察代码运行情况
表面看来通过 CustomerName 属性来访问 Form 与直接访问 Form 非常相似然而这种间接的窗体访问方式能够带来很多好处其中最重要的一点就在于它实现了更高的抽象性换言之哪怕你不知道 Form 中控件的任何细节 (比如窗体中是否包含 textbox 控件) 也能与 Form 交换数据你所要做的只是读取或设置 CustomerName 属性值而已有了这种抽象你就能在修改 Form 的实现时不影响项目中的其它代码因而大大简化了整个项目代码的维护单从本文的例子来看这种基于属性的窗体编程模式似乎并不比常规方式简单然而它以属性的形式隐藏了窗体的全部细节故能用简洁一致的代码来访问窗体所以它在一些相当复杂的用户界面编程中能够大显身手总而言之通过属性值来访问窗体及其控件的编程模式虽然不太直观却对程序员很有价值它不但比直接访问窗体的编程模式来得更专业而且让整个项目的代码清晰易读
五结论
Visual Basic NET 取消了早期版本中的默认窗体实例却引起了不少 NET 编程新手的困惑Visual Basic NET 规定只有通过引用窗体实例才能访问窗体的属性方法及其控件你所保存的窗体实例指针应该尽量让整个项目都能直接访问到它Visual Basic NET 的窗体处理机制已经变得更合理更强大可对于刚接触 NET 的程序员来说它的改进偏偏是造成许多困惑的根源