使用VB.NET的五个技巧
.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个类!在如此庞大的内容中却很容易找到有用的功能。我希望上面的几个技巧在你的应用程序中能够用到。