多窗体之间数据处理
最近我遇到很多关于多窗体之间数据交互处理的问题,如果你已经看过我的Forms over Data系列课程,你应该了解到:怎样建立一个database、连接它、保存你的数据、以及在代码中操作数据。这篇文章是对那些课程的扩展,这里我将利用一个示例描述怎样基于一个DataSet创建多窗体程序,我们将利用Northwind数据库中的Catagories表和Products表作为示例。
根据具体情况,实际上有很多方法可以将多个窗体连接到同一个DataSet上,这里我将用一个比较典型的例子来说明一下:从一个表格窗体中显示可编辑的详细信息窗体。
首先建立一个工程,将Form1窗体(也就是主窗体)的Text属性设置为Catagories and Products。
通过数据源窗口新建一个Data Source对象;选择SQL Server Database File,数据库为Northwind.mdf,选择Categories 和 Products两个表,将DataSet名称设为CategoriesProductsDataSet。
完成以后会有一个CategoriesProductsDataSet.XSD被创建(参见One-to-Many video)。当DataSet创建完成后,将CategoriesProductsDataSet中Categories 和 Products两个表从数据源窗口拖拽到Form1窗体中(注意:Products为Categories下面的节点)。
此时主窗体中的两个DataGridView将分别显示Categories和其相关的Products信息,窗口底部将会创建两个BindingSource,并且它们被绑定到了相应Grid的DataSource属性中,与此同时,有一个BindingNavigator工具条出现在窗体上。现在我们想要做的是创建第二个窗体以编辑产品的详细信息。
我首先对Categories 表和 Products表的TableNewRow事件处理方法添加了两行代码,以便使数据处理更简单,在这个事件处理方法中我只是对一些非空字段设置了默认值。如要打开DataSet的后台代码进行编辑,只要用鼠标右键点击Solution_Explorer窗口中的CategoriesProductsDataSet.XSD文件,接着选择"View Code"就可以进入代码编辑窗口,在这里你还可以对一些字段进行简单验证,就像我在video on adding validation中演示的那样。
接着我添加了一个新窗体Form2到工程中(也就是详细信息窗体,用来编辑产品的详细信息),将Form2的Text属性设置为Products Detail。Partial Class CategoriesProductsDataSet Partial Class CategoriesDataTable Private Sub CategoriesDataTable_TableNewRow(ByVal sender As Object, _ ByVal e As System.Data.DataTableNewRowEventArgs) _ Handles Me.TableNewRow 'Set defaults for non-nullable fields Dim category As CategoriesRow = CType(e.Row, CategoriesRow) category.CategoryName = "New Category" End Sub End Class Partial Class ProductsDataTable Private Sub ProductsDataTable_TableNewRow(ByVal sender As Object, _ ByVal e As System.Data.DataTableNewRowEventArgs) _ Handles Me.TableNewRow 'Set defaults for non-nullable fields Dim product As ProductsRow = CType(e.Row, ProductsRow) product.ProductName = "New Product" product.Discontinued = False End Sub End Class End Class
将数据源窗口中Products的显示属性设为details,并将它拖拽到Form2中,这次用不到BindingNavigator工具条,将它删除,并在窗体底部添加两个按钮:OK和Cancel。
然后删除下面的ProductsTableAdapter组件,打开Form2窗体的后台代码,在Form.Load事件处理方法中删除编辑器自动生成的Fill代码,因为我们不需要重新从数据库中填充DataSet,而是将数据从主窗体传递到详细信息窗体中。
此时,传递数据最简单的方式是在详细信息窗体的后台代码中创建一个新的构造函数,将主窗体上我们正在编辑的CategoriesProductsDataSet和打算编辑的Product行的主键作为参数。这样我们就可以在详细信息窗口中设置ProductBindingSource的DataSource 和 Filter 属性值, 以使它显示选中的行,从而保持主窗体和详细信息窗体同步。在详细信息窗体后台代码中类定义的下方输入“Sub New”, 并按回车键来自动生成正确的构造函数调用,接着修改它的签名和设置ProductBindingSource属性。
回到主窗体中,在工具条上添加一个按钮,并把他的Text属性设为Edit Product Detail,双击按钮进入事件处理方法,加入如下代码:Sub New(ByVal ds As CategoriesProductsDataSet, ByVal id As Integer) ' This call is required by the Windows Form Designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. ' Set the DataSource of the BindingSource and then set the Filter ' so that the correct row will be displayed on the detail form. Me.ProductsBindingSource.DataSource = ds Me.ProductsBindingSource.Filter = "ProductID = " & id.ToString End Sub
此方法中,首先调用了ProductsBindingSource.EndEdit(),这样在打开详细信息窗体编辑Product之前,所有在主窗体中所做的改变被保存到DataSet中。将ProductBindingSource.Current属性赋值给ProductRow来获取当前Product行,并将ProductID和主窗体中的CategoriesProductsDataSet传递给详细信息窗体的构造函数。Private Sub ToolStripButton1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles ToolStripButton1.Click Me.ProductsBindingSource.EndEdit() If Me.ProductsBindingSource.Position > -1 Then 'Get the current product row Dim row As CategoriesProductsDataSet.ProductsRow row = CType(CType(Me.ProductsBindingSource.Current, _ DataRowView).Row, CategoriesProductsDataSet.ProductsRow) 'Open the product detail form passing the dataset and the product ID Dim frm As New Form2(Me.CategoriesProductsDataSet, row.ProductID) frm.Show() End If End Sub
这里我允许用户打开任意数量的详细信息窗体,但是如果你想一次只打开一个窗体,只需要将frm.Show()修改为frm.ShowDialog();这个例子演示了编辑当前行,如果没有任何行被选中,将不会弹出窗体;这种情况下,如果你想在详细信息窗体被打开之前自动添加一新行,可以在EndEdit()之前调用ProductBindingSource.AddNew()(提示:如果这样做,那么在DataTable partial class中像我前面所描述的那样,对非空字段设置默认值是非常重要的)。
最后对按钮OK、Cancel和Form2 Close添加事件处理方法,以确认是否保存对Product所做的修改。代码如下:
我已附加了一个完整的基于Northwind数据库的代码示例,里面也包括了我在这个视频和这篇文章中演示的怎样恰当地保存相关的DataTables。Private Sub Form2_FormClosing(ByVal sender As Object, _ ByVal e As System.Windows.Forms.FormClosingEventArgs) _ Handles Me.FormClosing Me.ProductsBindingSource.CancelEdit() End Sub Private Sub cmdCancel_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdCancel.Click Me.ProductsBindingSource.CancelEdit() Me.Close() End Sub Private Sub cmdOK_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdOK.Click Me.ProductsBindingSource.EndEdit() Me.Close() End Sub
记住,要在窗体中很好地对数据进行处理,关键是使用BindSource对象,这个类使数据处理和窗体控制变的非常简单。更多信息请参见Understanding Data video。