Visual Basic .NET 中的数据绑定
原文地址:http://www.microsoft.com/china/msdn/library/langtool/vbnet/DataBindinginVBNET.mspx
下载本文的代码: AdvancedBasics0308.exe (223KB)
问 在 Visual Basic .NET 中编写代码时,如何充分利用应用程序中的数据绑定?
答 正如我在 Advanced Basics 上个月的专栏中提到的那样,数据绑定在 Microsoft .NET 框架中仍然存在并且能够很好地工作,这个伟大的技术使数据的处理在许多类型的应用程序中更有用。 让我们接着上期专栏的结尾来看一些实际的数据绑定应用程序。
让我们首先讨论一下结构。 图 1 概述了结合使用数据绑定和数据源的常见方法。 通过数据库从数据源提取数据,并将它放到非类型化 DataSet 中。 之所以使用非类型化 DataSet,是为了允许使用通用数据访问库。 这允许您调用可返回任何 DataSet 的单个函数。 现在可以提取这个非类型化 DataSet,并通过使用两个匹配表来将它与类型化 DataSet 合并。 最后,可以将控件绑定到类型化 DataSet。

图 1 绑定
您可能想知道为什么不直接绑定到非类型化 DataSet。 这个问题问得很好,因为类型化 DataSet 可提供诸如字段早期绑定之类的功能。 这允许您将 .CustomerName 用作 DataSet 的成员(而不是用值 "CustomerName" 引用 Items 集合),从而大大简化数据的访问过程。 这种早期绑定还允许您在设计时提供对数据绑定的自动支持。 这使您能够在编辑器中选择字段,甚至在某些控件中访问自定义属性。
让我们看一下现实中的数据绑定。 请查看下面的函数,该函数摘自我于 2003 年 7 月撰写的专栏,它从 SQL Server™ 提取数据并返回一个 DataSet:
Function RetrieveCustomerContacts() As DataSet
Dim ds As DataSet
Try
ds = RunSQLWithDataSet("Select CustomerID, " & _
"CompanyName, ContactName, NoOfCustomerVisits " & _
"from customers", ConnectionString, "Customers")
Catch ex As Exception
ds = Nothing
End Try
Return ds
End Function
接着,考虑一个名为 Customers 且具有下列字段的类型化 DataSet,我已经将这些字段作为 dsCustomers.xsd(为了节省空间,我忽略了部分 XSD)添加到项目中:
<xs:sequence> <xs:element name="CustomerID" type="xs:string" /> <xs:element name="CompanyName" type="xs:string" /> <xs:element name="ContactName" type="xs:string" minOccurs="0" /> <xs:element name="NoOfCustomerVisits" type="xs:int" minOccurs="0" /> </xs:sequence>
在 Visual Studio .NET 中,可通过以下方法将此数据绑定到 DataSet:将 DataSet 组件从工具箱拖放到您的窗体上,然后将它与名为 Customers 的类型化 DataSet 进行关联。 现在,可使用 DataGrid 控件的属性窗口,可以将新的 DataSet 选作 DataSource,将 Customers 表选作 DataMember。
此过程可通过在窗体的 Load 事件中添加以下代码来完成:
Dim ds As DataSet Dim oClass As New SomeComponent.Class1 Try ds = oClass.RetrieveCustomerContacts dsCustomers.Merge(ds) Catch ex As Exception sbarmain.Text = ex.Message End Try
上面的代码调用 RetrieveCustomerContacts 类以检索 DataSet,并将检索到的 DataSet 合并到窗体的类型化 DataSet 中。 当然,使用其他控件(例如,列表框和组合框控件),也可以执行这种类型的数据绑定。 还有一个有用的绝佳功能。 使用我刚提到的同样代码,可以将类型化 DataSet 加载到您的组合框中。 然后,只需使用 SelectedIndexChanged 事件中的一行代码即可提取所选值:
txtSelectedID.Text = ComboBox1.SelectedValue
当然,如果要将 txtSelectedID 控件绑定到属性名为 CustomerID 的类或结构,则可以进行这样的绑定,而不将数据直接放到该控件中:
CustomerContacts.CustomerID = ComboBox1.SelectedValue
这将更新该类的 CustomerID 字段,而且数据绑定将更新绑定文本框中显示的值。
让我们了解另一种数据绑定方法,该方法使得由数据驱动的应用程序对于用户来说更友好。 例如,假设您的应用程序必须在三个文本框中显示 DataSet 或其他可绑定对象的数据。 CurrencyManager 可用于在这三个控件之间导航并提供其他功能。 我看过几个使用 CurrencyManager 的示例,它们相当笨拙,因此我借用 MSDN 库中某个示例的概念,并将其变得更实际。 图 2 显示此窗体的界面。

图 2 Currency Manager 界面
此窗体允许您浏览 DataSet 并在浏览时对数据进行改变。 每次对值进行更改并移到下一条记录时,所做的更改会存储到 DataSet 中, 这与数据绑定在 Visual Basic 6.0(和更低版本)中的工作方式几乎相同。 现在,Visual Basic .NET 使其更加灵活,因为您可以访问数据绑定的低级功能。
让我们看一下图 3 中的代码,以便让我们分享我所学到的一些东西。 代码相当简单,但是需要一点解释。 我将忽略例行代码部分(例如,从 SQL Server 提取数据并返回它的类),但是,本专栏的下载文件中包含整个代码。
Private WithEvents thisCurrencyManager As CurrencyManager2

3
'Variable defs go here4

5
'Form Load builds the dataset, merges it with CustomerInfo1, 6
'then binds the controls by calling BindControls7

8
Private Sub BindControls(ByVal thisDataTable As DataTable)9
' Bind a TextBox control to a DataTable column 10
'in a DataSet.11
txtCompany.DataBindings.Add("Text", thisDataTable, "CompanyName")12
txtName.DataBindings.Add("Text", thisDataTable, "ContactName")13
txtCity.DataBindings.Add("Text", thisDataTable, "City")14
txtCustomerID.DataBindings.Add("Text", thisDataTable, "CustomerID")15

16
' Specify the CurrencyManager for the DataTable.17
thisCurrencyManager = _18
CType(Me.BindingContext(thisDataTable), CurrencyManager)19
' Set the initial Position of the control.20
thisCurrencyManager.Position = 021
End Sub22

23
Private Sub MoveNext(ByVal thisCurrencyManager As CurrencyManager)24

25
If thisCurrencyManager.Position = thisCurrencyManager.Count - 1 Then26
MessageBox.Show("You're at end of the records")27
Else28
thisCurrencyManager.Position += 129
CheckChanges()30
End If31
End Sub32

33
Private Sub MoveFirst(ByVal thisCurrencyManager As CurrencyManager)34

35
thisCurrencyManager.Position = 036
CheckChanges()37
End Sub38

39
Private Sub MovePrevious(ByVal thisCurrencyManager As CurrencyManager)40

41
If thisCurrencyManager.Position = 0 Then42
MessageBox.Show( _43
"You're at the beginning of the records.")44
Else45
thisCurrencyManager.Position -= 146
CheckChanges()47
End If48
End Sub49

50
Private Sub MoveLast(ByVal thisCurrencyManager As CurrencyManager)51

52
thisCurrencyManager.Position = thisCurrencyManager.Count - 153
CheckChanges()54
End Sub55

56
'Button click events go here57

58
Private Sub txtName_TextChanged(ByVal sender As System.Object, _59
ByVal e As System.EventArgs) Handles txtName.TextChanged60
'Exit if starting up61
If IsNothing(thisCurrencyManager) Then62
Exit Sub63
End If64

65
StartEditMode()66

67
End Sub68

69
Private Sub txtCompany_TextChanged(ByVal sender As _70
System.Object,ByVal e As System.EventArgs) _71
Handles txtCompany.TextChanged72
StartEditMode()73
End Sub74

75
Private Sub txtCity_TextChanged(ByVal sender As _76
System.Object, ByVal e As System.EventArgs) _77
Handles txtCity.TextChanged78
StartEditMode()79
End Sub80

81
Sub StartEditMode()82
cmdSaveChanges.Enabled = True83
End Sub84

85
Sub EndEditMode()86
Me.BindingContext(CustomerInfo1.Customers).EndCurrentEdit()87
End Sub88

89
Private Sub thisCurrencyManager_PositionChanged( _90
ByVal sender As Object, _91
ByVal e As System.EventArgs) _92
Handles thisCurrencyManager.PositionChanged93
cmdSaveChanges.Enabled = False94
End Sub95

96
Private Sub cmdNew_Click(ByVal sender As System.Object, _97
ByVal e As System.EventArgs) Handles cmdNew.Click98
thisCurrencyManager.AddNew()99
End Sub100

101
'Other subs go here图3 数据绑定的关键代码元素
图 3 中的第一行代码为我的应用程序定义 CurrencyManager。 它用 WithEvents 进行定义,以便允许我在需要时使用它的事件:
Private WithEvents thisCurrencyManager As CurrencyManager
frmBinder2_Load 事件中的代码相当标准。 它调用一个可返回 DataSet 的方法,然后将返回的 DataSet 合并到类型化 DataSet 中。 类型化 DataSet 的 Customers 表传递到可执行绑定的 BindControls 方法。
BindControls 方法就是 CurrencyManager 在其中操作的方法。 前几行代码将该 DataSet 中的四个字段绑定到控件的 Text 属性:
txtCompany.DataBindings.Add("Text", thisDataTable, "CompanyName")
txtName.DataBindings.Add("Text", thisDataTable, "ContactName")
txtCity.DataBindings.Add("Text", thisDataTable, "City")
txtCustomerID.DataBindings.Add("Text", thisDataTable, "CustomerID")
实际上,BindControls 的最后两行代码使用 CurrencyManager。 倒数第二行将 CurrencyManager 设置为数据源(在本例中为 DataTable)的 BindingContext:
thisCurrencyManager = CType(Me.BindingContext(thisDataTable), _ CurrencyManager)
最后一行将初始位置设置为 0(第一条记录):
thisCurrencyManager.Position = 0
现在,我可以继续使用 CurrencyManager。 所有的 Move 方法都通过使用 CurrencyManager 来改变记录在 DataSet 中的位置。 例如,只要当前的位置不是最后一个元素,MoveNext 方法就在数据源中向前移动当前的元素指针:
If thisCurrencyManager.Position = _
thisCurrencyManager.Count - 1 Then
MessageBox.Show("You're at end of the records")
Else
thisCurrencyManager.Position += 1
CheckChanges()
End If
在位置每次发生改变时,都调用 CheckChanges 方法,以便确定 DataSet 是否也发生了更改。
我在构建这个示例应用程序时,被一个小问题困扰了几个小时。 我可以很好地滚动记录,并且当我将位置改变到另一行时,对上一行的更改生效。 但是,当我使用 Save 按钮保存所做的更改时,GetChanges 方法无法正确地指出已发生更改。 我按照 MSDN 文档和联机示例检查代码,在进行了一些探查之后,我找到了问题所在。
我将 DataTable 用作了数据源。 EndEditMode 过程用下面的代码行调用了 EndCurrentEdit 方法:
Me.BindingContext(CustomerInfo1, "Customers").EndCurrentEdit()
该代码不工作;即使数据确实发生了更改,DataSet 也显示未进行更改。 最后,我认识到所使用的上下文有误。 下面的上下文才能够正常工作:
Me.BindingContext(CustomerInfo1.Customers).EndCurrentEdit()
在本例中,我只传递 DataTable,而 DataTable 的确是这些控件的数据源。 可笑的是,最初的代码在执行过程中不产生任何错误,而只是不工作。 我之所以确信这行代码的确能够执行,是因为当我将表名 Customers 改为 CustomersX 时,这行代码生成了一个运行时错误。 正如我说过的那样,该代码能够执行,只不过它指向了错误的数据源。
此窗体中的其他代码相当简单,因为它们实现对 move 方法的调用并且处理与 CurrencyManager 的交互。
一定要注意 cmdAddNew_Click 事件,因为它导致在数据中添加一个新行。 这会刷新窗体并允许您在新行中输入数据。 通过离开和回到该行可以看到自动保存了更改。
thisCurrencyManager_PositionChanged 事件会在位置发生改变时引发。 此事件用于重置 SaveChanges 按钮的状态。
有关数据绑定的详细信息,请参阅 Billy Hollis 的 "Not Your Father's Data Binding" 一文。 有关数据绑定的更多信息,请参阅 MSDN Magazine 中的 Data Binding。
请将您提给 Ken 的问题和意见发送至 basics@microsoft.com。
Ken Spencer 为 32X Tech (http://www.32X.com) 工作,他就 Microsoft 技术提供培训、软件开发和咨询服务。
来自 2003 年 8 月发行的 MSDN Magazine。
可以从当地报摊或最好通过订阅购买该杂志。

浙公网安备 33010602011771号