UML软件工程组织

使用VB.NET的五个技巧
作者:陶刚 本文选自:赛迪网 2003年04月10日

 

  .NET框架组件太大了,比任何以前所写的封装功能的库都要大。这样有好处,因为它大幅削减了建立应用程序所需编写的代码,但是也使我们不可能完全了解该框架组件。但是我们很容易从中找到一些技巧。



  窗体嵌套

  经验丰富的Visual Basic开发者知道多文档界面(MDI)应用程序能够包含子窗体,那些子窗体由MDI父窗体管理。但是如果没有MDI的能力你怎样实现包含嵌套窗体?例如一个MDI子窗体也可能需要包含另一个窗体。

有时能够使用用户控件(UserControl)实现这种功能,但是如果你真的需要把一个窗体嵌套进另一个窗体,有多种方法可以实现。窗体衍生自Control类,这意味着它能被放入另一个窗体的控件集合中,使用如下的逻辑:

Dim f As New frmEmbed2()
Me.Controls.Add(f)
f.Show()


  但是很不幸,这段代码将会导致一个运行时(runtime)异常(见图1)。

  

  图1.试图把一个窗体添加到另一个窗体的控件集合时出现的运行时错误

  为了避免这种异常,该窗体的TopLevel属性必须设置为False(见下面的代码)。

Dim f As New frmEmbed2()
f.TopLevel = False
Me.Controls.Add(f)
f.Show()


  图2显示的是使用上面的逻辑实现的一个窗体嵌入另一个窗体。嵌入的窗体有一个标题条(它的颜色是未激活的系统颜色),因此该嵌入窗体能在容器窗体内四处拖动。在图2中,该窗体从它的开始位置(左上角)拖到了右下角。

  

  图2.在容器窗体中有一个嵌入的窗体。嵌入的窗体能在容器窗体中拖动。

  通常在显示嵌入的窗体前先设置它的位置。这只需要简单的设置嵌入窗体的Left和Top属性。嵌入窗体的位置与容器窗体是相对的。

  与MDI子窗体不同,嵌入窗体能覆盖容器窗体上的控件。图3显示了它们的不同。

  

  图3.嵌入窗体(左)可以覆盖容器窗体上的控件。MDI子窗体(右)不能覆盖MDI父窗体上的控件。

  在右边的MDI例子中,没有办法使按钮隐藏在子窗体的后面。但是在左边该按钮被嵌入窗体覆盖了。

  当窗体第一次被嵌入时,它将显示在容器窗体上的已存在的控件的后面。当它被点击时,它走向前台并停留在那儿。这会打扰用户,但是能通过插入下面的代码防止这种情况发生:

f.BringToFront()


  嵌入的窗体可以包含其它的嵌入窗体,没有实际的限制。图4显示了一个本身包含嵌入的窗体的嵌入窗体。

  

  图4.一个包含嵌入窗体的嵌入窗体

  处理数据行(DataRow)

Windows窗体中的数据绑定列表框和组合框很节省时间。典型的代码如下(假定已经建立了SqlDataAdapter或者其它部件获取数据):

Dim ds As New DataSet()
SqlDataAdapter1.Fill(ds, "Customers")
ListBox1.DataSource = ds.Tables("Customers")
ListBox1.DisplayMember = "CompanyName"
ListBox1.ValueMember = "CustomerID"


  在这种情况下,代码使用Northwind数据库的顾客记录工作。DisplayMember属性设置为你希望用户在列表框中看到的记录字段,它是customers表的CompanyName。通常ValueMember属性设置为数据表中的一个键字段,对于customer来说是CustomerID。一旦用户选择了列表框中的一行,很容易使用列表框的SelectedValue属性获得键字段:

MsgBox(ListBox1.SelectedValue)


  但是有可能需要一个与被选择项相关的整个数据行对象的引用。例如,如果被选择的行需要被删除,就不知道键了。你需要一个数据行的引用以使用Delete方法。

  典型的Visual Basic开发者通常这样想:"我已经得到了该行的键了,我将编写一些逻辑来查找使用该键的行"。这样可以实现,但是有更好的实现方法。可以使用一行代码获取与列表框中选项关联的数据行:

Dim dr As DataRow = CType(ListBox1.SelectedItem, DataRowView).Row


  通常该逻辑不会凭直觉出现,即使对经验丰富的开发者。为了解释这是怎样实现的,我把上面的一行拆成几行,下面的代码与上面代码的功能相同:

Dim drv As DataRowView
drv = CType(ListBox1.SelectedItem, DataRowView)
Dim dr As DataRow
dr = drv.Row


  DataRowView类是数据行的包装,它被多个Windows窗体控件使用。它使得显示与控件中的数据行相关的数据更加容易。当列表框被数据绑定到数据表时(假定列表框中的有些行当前被选定了),列表框的SelectedItem属性保存了一个DataRowView对象。

  这意味着我们能把列表框的SelectedItem属性转换到DataRowView对象,这就是上面代码中的第二行实现的。接着DataRowView暴露一个Row属性,它指向被包装的数据行。上面的代码声明了一个数据行并设置了Row属性。

  转换对象的类型以访问它的接口的技术在Visual Basic 6.0中不是经常使用,但是在Visual Basic .NET中这是经常的。有了上面的例子后,大多数有经验的开发者迅速跟上了这种技术。

  数据行的引用(dr)可用于用任何方式维护行。访问数据行中的任何特定字段是可行的。行中的数据可以被改变,能使数据行的Delete方法把该行标识为删除,或者从数据表的行集合中删除该行。下面的代码标识删除了一行:

dr.Delete()


  使用主键(由ListBox.SelectedValue返回)查找下层数据行的方法需要很多代码,要花很长时间,执行起来更慢。对于刚开始使用Visual Basic .NET的程序员来说花几个小时编码是很正常的。理解上面的技术节约了很多时间,更简单、容易维护代码。



  给控件绑定颜色

  数据绑定能应用于控件的任何属性。我看到过很多人提到能够绑定文本框的背景颜色到数据项,举个例子,超期的帐号的背景色显示红色。

  但是如果你试图使用数据集或者数据表实现该功能,将会遇到问题。数据行只能保持受到限制的数据类型,并且不支持Color类型。如果你不能把颜色存储在数据中怎么能绑定颜色呢?

  有些途径可以解决这个问题,但是最简单的是用绑定到自定义数据对象代替绑定到数据表。自定义业务对象的属性可能是Color型的,这样的属性能绑定到控件的BackColor属性。

  为了演示,我定义了下面的自定义事务对象:

Public Class Account

Dim m_nAccountID As Integer
Dim m_sCustomerName As String
Dim m_dblBalance As Double

Public Sub New(ByVal nAccountID As Integer, ByVal sCustomerName As String, _
   ByVal dblBalance As Double)
    Me.AccountID = nAccountID
    Me.CustomerName = sCustomerName
    Me.Balance = dblBalance
End Sub

Public Property AccountID() As Integer
    Get
        Return m_nAccountID
    End Get
    Set(ByVal Value As Integer)
        m_nAccountID = Value
    End Set
End Property

Public Property CustomerName() As String
    Get
        Return m_sCustomerName
    End Get
    Set(ByVal Value As String)
        m_sCustomerName = Value
    End Set
End Property

Public Property Balance() As Double
    Get
        Return m_dblBalance
    End Get
    Set(ByVal Value As Double)
        m_dblBalance = Value
    End Set
End Property

Public ReadOnly Property BackColor() As Color
    Get
        If m_dblBalance < 0 Then
            Return Color.Salmon
        Else
            Return SystemColors.Window
        End If
    End Get
End Property
End Class


  注意只读的BackColor属性从Balance属性中得到值,并且为负平衡(negative balance)暴露了一个不同的颜色。该类的其它元素很直接。

  现在我们建立一个界面来操作这些对象的集合(见图5)。

  

  图5.演示背景颜色绑定的窗体(设计时)

  上面的三个文本框都用于保持当前Account对象的数据。它们分别叫txtAccountID、txtCustomerName和txtBalance。显示Load的按钮叫btnLoad,用于载入帐号集合。另两个按钮在记录间导航,分别叫btnBack 和 btnForward。

  帐号对象集合可以保持在ArrayList(数组列表)中,因此下面一行代码应该在窗体代码的最前面:

Dim colAccounts As ArrayList


  下面是Load方法的Click事件代码。它建立了一些Account对象并把它们放入一个集合中,接着把该集合绑定到文本框。

colAccounts = New ArrayList()
colAccounts.Add(New Account(1, "ABC Company", 10))
colAccounts.Add(New Account(2, "XYZ, Inc.", -10))
colAccounts.Add(New Account(3, "MNP Limited", 0))

txtAccountID.DataBindings.Add(New _ 
             Binding("Text", colAccounts, "AccountID"))
txtCustomerName.DataBindings.Add(New _ 
             Binding("Text", colAccounts, "CustomerName"))
txtBalance.DataBindings.Add(New _
             Binding("Text", colAccounts, "Balance"))
txtBalance.DataBindings.Add(New _ 
             Binding("BackColor", colAccounts, "BackColor"))


  注意最后两行。txtBalance的Text属性绑定到一个帐号的Balance属性,并且该控件的BackColor属性绑定到帐号对象的BackColor属性。它演示了.NET框架组件绑定一个以上属性到不同数据项。

  现在点击btnBack的Click事件,填入一下代码:

If Me.BindingContext(colAccounts).Position > 0 Then
    Me.BindingContext(colAccounts).Position -= 1
End If
在btnForward的Click事件中写入以下代码:
If Me.BindingContext(colAccounts).Position < colAccounts.Count - 1 Then
    Me.BindingContext(colAccounts).Position += 1
End If


  启动项目并点击Load按钮。ABC公司的记录出现在文本框中。点击向前按钮,就是XYZ公司记录,同时,txtBalance的背景色变为橙红色(见图6)。

  

  图6.数据绑定窗体显示了一个负平衡记录,引起Balance字段的背景色不同

  过了该帐号记录后,该文本框的背景颜色将变回正常色。

  Account类不是特别复杂。但是这个例子最少让你看到了怎样绑定不同属性(例如控件颜色)。



  修改数据窗体向导

  使用数据窗体向导(Data Form Wizard)你能迅速获得文件操作程序窗体。为了使用它,选择Project菜单的Add New Item,接着选择Data Form(数据窗体)。该向导将一步一步帮助你指定希望的数据,并为那些数据建立一个文件操作程序。图7显示了一个从Northwind数据库的Products表中产生的数据窗体。

  

  图7. Northwind Products表的文件维护窗体,它由数据窗体向导产生

  但是这种自动生成程序有一个重要的限制。如果被访问的数据有任何字段不能为空(因为数据库大纲不允许空值),那么向导生成的程序不能添加记录。当点击Add按钮时,将出现错误信息,提示记录中的第一个字段不允许为空(如果你没有最新的服务包,你也许看不到该错误信息,但是程序拒绝添加记录)。

  该问题是由于数据窗体向导使用BindingContext对象给绑定的数据表添加了一行。下面是btnAdd_Click事件程序失败的代码:

Me.BindingContext(objProducts, "Products").AddNew()


  解决方法是为新行略过BindingContext对象。下面是添加新行的典型代码,该代码应该代替上面的一行代码:

Dim dr As DataRow
dr = objProducts.Tables("Products").NewRow
dr.Item("ProductName") = ""
dr.Item("Discontinued") = False
' Set any other fields that cannot null to default values.
objProducts.Tables("Products").Rows.Add(dr)


  在用数据表的NewRow方法获得一个空行时,该代码给不能为空的字段填充值。接着数据表接受新行,通过数据表行集合的Add方法添加新行。

  有了这个补丁后,该数据程序能够运行。可以对它进行增强或改变,例如改变SupplierID 和CategoryID字段以从包含供应商和类别的下拉列表中选择。



  在.NET框架组件中显示时间

  开发过程过程中我们通常对特定代码片运行所花的时间很感兴趣。当然有一些标准程序和代码工具可以查看到它,但是有经验的Visual Basic 6.0开发者有更快的办法。仅仅捕捉开始时间(使用Now关键字)和终止时间(再次使用Now关键字),两种相减,就能知道结果了。

  如果使用Visual Basic .NET编写,首先尝试的代码可能是这样的:

Dim StartTime As DateTime = Now
' {code to check for timing goes here}
Dim EndTime As DateTime = Now
Console.WriteLine((StartTime - EndTime).ToString)


  但是这段代码的最后一行有语法错误。错误消息是"日期类型没有定义'-'操作符"。这意味着我们不能执行减法。日期数据类型不支持减法操作,那么我们怎么得到两次时间的差别呢?

  答案就是使用TimeSpan类。它是用于保持时间段的。上面的代码看起来与.NET框架组件中的相似:

Dim StartTime As DateTime = Now
' {code to check for timing goes here}
Dim EndTime As DateTime = Now
Dim RunLength As System.TimeSpan
RunLength = EndTime.Subtract(StartTime)
Console.WriteLine(RunLength.ToString)


  计算使用的是类Date的Subtract方法。最后一行将输出时间的跨度,格式化成小时、分钟和秒(包括秒的小数位)。典型的输出是这样的:

00:00:10.4850768


  该时间跨度是10秒半。尽管显示了7位小数,但是只能相信两位,但是已经足够了。



  结论

  .NET是一种有趣的技术。.NET框架组件有超过8000个类!在如此庞大的内容中却很容易找到有用的功能。我希望上面的几个技巧在你的应用程序中能够用到。




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