Using Data Across Multiple Windows Forms

Using Data Across Multiple Windows Forms

Rate This
  • Comments 38

Recently I've had more than a few questions about how to handle working with data across multiple forms. If you've watched my Forms over Data video series you know how to create a database, connect to it, save your data properly, and work with data in your code. In this post I'll expand upon those videos and walk through an example of how to create multiple forms that work against the same DataSet. We'll use the Northwind Categories and Products for this example.

There are actually many ways to connect multiple forms to the same DataSet depending on your scenario, but I'll take a common example of displaying editable detail forms from a main form with a grid-style edit like this:  

Here we've got a form with two DataGridViews displaying Categories and their related Products. Once you connect to the Northwind Database with the Data Sources Window, you can choose Categories and Products tables and that will create a DataSet and the corresponding TableAdapters for you just like I showed in the One-to-Many video. Once your DataSet is set up then you can drag and drop the Categories and related Products tables from the Data Sources Window to your form. This will set up the two BindingSources and bind the grids properly as well as add a BindingNavigator ToolStrip to the form. Now what we want to do is create a second form that allows us to edit the product details.

First I added a couple lines of code to the TableNewRow event handler on the Categories and Products DataTables so that it would be easier working with the data. In this handler I just set some defaults for the non-nullable fields. To get to the code behind file for your DataSet just right-click on the DataSet in the Solution Explorer and select "View Code". This is where you can add simple validation as well like I showed in the video on adding validation.

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

Next I added a new form to the project and again used the Data Sources Window to drag and drop the Product fields I want to edit onto the new form. This time, however, I don't need the BindingNavigator ToolStrip so I just selected that and deleted it and instead added two buttons on the bottom of the form for OK and Cancel.

 

Next I deleted ProductsTableAdapter in the component tray and then opened up the code-behind of the detail form. I then deleted the Form.Load handler with the Fill code that was auto-generated for us. This is because we do not want to re-fill the DataSet from the database, instead we are going to pass the data to this form from our main form.

The easiest way to pass the data in this situation is to create a new constructor that accepts the DataSet we're editing on the main form and the primary key of the product we want to edit. Then we can set the ProductBindingSource's DataSource and Filter property on the detail form so that the it displays the selected row. This keeps the main form and the detail form edits in sync. In the details form code right under the class definition type "Sub New" and hit enter to auto-generate the correct constructor call, then change the signature and set the ProductBindingSource properties.

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

Then back in the main form I created a button on the ToolStrip that opens this details form. I call ProductsBindingSource.EndEdit() first so that any changes made on the main form are pushed into the DataSet before we attempt to edit the row on the detail form. Then I get the current Product row by casting the ProductBindingSource.Current property to ProductsRow and pass the ProductID to the detail form's constructor along with the reference to the CategoriesProductsDataSet on the main form:

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

You can open the details form modal or modeless. Here I'm allowing the user to open as many detail forms as they want but if you want to only open one at a time then just change the frm.Show() line to frm.ShowDialog() instead. This example demonstrates editing the current row and does nothing if there is no product row selected. You could optionally add a call to ProductBindingSource.AddNew() before the EndEdit() call in this scenario if you need to add a row programmatically before the form is opened. (NOTE: It's important that you add defaults for non-nullable fields like I showed above in the DataTable partial classes if you call AddNew() then EndEdit() in succession.)

The last bit of code is on the detail form that calls EndEdit() or CancelEdit() on the ProductsBindingSource depending on if they clicked Cancel or OK on the detail form, or just closed the form without selecting OK. This will either accept or discard the changes made to the product row on the particular detail form.

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

I've attached a complete example to this post that works against the Northwind database. It also shows how to properly save related DataTables like I demonstrated in this video and this post. Remember the key to working with data on your forms is to use the BindingSource object as this class really makes working with data and form controls very simple. For more information see the Understanding Data video.

Enjoy!

Attachment: MultiFormSample.zip
Leave a Comment
  • Please add 2 and 5 and type the answer here:
  • Post
  • PingBack from http://www.artofbam.com/wordpress/?p=4343

  • Hi Beth,

    Another great topic.  One question,  How can we control the application so that if a particular detail form is already open, it would just activate the particular form and not open another form.

    Warren

  • Hi Warren,

    In this case you could create a Dictionary(Of Integer, Form) and check to see if the ID was already open on a detail form, if not then add it to the dictionary and hook up an explicit handler to remove the form from the list when the form closed.

    -B

  • Great article. Just what I was looking for.

  • Hi Beth, I went through your article but it didn't really solve my problem. I actually want to edit or insert values into my database from a second form that displays details of a selected row in the first form of my application. I should then be able to save changes from the detail form. I am using stored procedure to pull data from related tables. Pls help me out.

  • I don't think I've used another programming article as much as this one.  Thanks for the work.  One question - I've created a dataview where the source is my passed dataset but I can't get it to return and save the changes.  Suggestions?

  • Hi Celestine,

    You really don't need to share data across the forms in that case (you could just load the individual detail row on the second form using a parameterized query on your detail TableAdapter passing the parent FK) but if you want to save the entire parent form from a button on the detail form you could either create and raise an event from the detail form and handle it on the parent form or you could pass a reference to the parent form to the detail form when you create it and then expose a public Save method on the parent form that you can call.

    I'd probably just do the easy thing and work with a separate DataTable of the details -- when you drag-drop the table onto the detail form, a new BindingNavigator is created which sounds like what you want. Just create a new DataSet with your detail table and add a parameterized query to the TableAdapter that passes the parent's foreign key. You can pass the key into the constructor of the detail form and save it in a property then use it in your Load handler when the DataTable is filled using your parameterized query.

    HTH,

    -B

    HTH,

    -B

  • Hi Charlie,

    Sounds like you're just missing a Validate/EndEdit call sequence on your detail form. With a DataGridView you need to call Validate on the form. Try something like:

    Private Sub cmdOK_Click(ByVal sender As System.Object, _

    ByVal e As System.EventArgs) Handles cmdOK.Click

       Me.Validate()

       Me.ProductsBindingSource.EndEdit()

       Me.Close()

    End Sub

    HTH,

    -B

  • For Warren:

    Another great topic.  One question,  How can we control the application so that if a particular detail form is already open, it would just activate the particular form and not open another form.

    Reply:

    You can try adding a public property of ID to the Detail form and then enumerate each Open form and check the type of form then drill down for ID if found then activate that form.

    Example:

    ' CHANGES IN PRODUCT DETAIL FORM

    Private _ProdID As Integer

    Public ReadOnly Property ProductID() As Integer

           Get

               Return Me._ProdID

           End Get

    End Property

    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

       Me._ProdId = id    ' NEWLY ADDED LINE TO SET THE ID

    End Sub

    Now whenever you will be showing the product detail use the code

    For Each f As Form in My.Application.OpenForms

    If TypeOf f Is frmProductDetail Then

    If CType(f, frmProductDetail).ProductID = id Then

    f.Show

    f.Activate

    Exit For

    End

    End

    Next

  • Hi Beth!!

    Thanks for your great articles and help!!!

  • Hi Charlie, thanks a lot for this article !!!

    Good job!

    Laurent

  • I am extremely grateful for this article. As a business manager rather than an IT professional these types of articles provide encouragement to take the .net plunge. But I have a problem and I cannot solve it by googling....

    I have a main form with a datagrid as per the example and a detail form. The dataset is shared as in your example and the detail form saves the updates to the dataset via the bindingmanager with no problem.

    I need to be able to save the dataset changes to the database from the detail form. I have tried sharing tha table adapter on the main form but without success. I then decided it would be best to raise an event which fires a save event in the main form which already works. I get so far and then find I dont know how to set up the event handler.

    Can anyone advise how best to set up a public save method for this example which can be used say within the published article.

    Very tired and frustrated - any help will be enormously appreciated.

  • Hi Martin,

    Be careful here because what your asking affects scalability of your application. It's better to make chunky not chatty calls to the backend. However this may be necessary for your particular scenario so what you could do is create a public event on the child form and then from the parent you could handle this event.

    Something like...

    On the child, declare the event in the Form class:

    Public Event Saved(ByVal sender As Object, ByVal e As EventArgs)

    The raise the event when you want to save (like in the OK click event handler):

    RaiseEvent Saved(Me, EventArgs.Empty)

    Then handle this on the parent. When you create the form instance add an explicit handler and write a handler that calls Save:

    ...

     'Open the product detail form passing the dataset and the product ID

         Dim frm As New Form2(Me.CategoriesProductsDataSet, row.ProductID)

         AddHandler frm.Saved, AddressOf Me.ChildForm_Saved

         frm.Show()

      End If

    End Sub

    Private Sub ChildForm_Saved(ByVal sender As Object, ByVal e As EventArgs)

       Me.Save()

    End Sub

    HTH,

    -B

  • Hi Beth

    Many, many thanks for such a quick and helpful response which is much appreciated. My efforts to handle the event on the parent form had failed but I can now see where I have been going wrong. Thankyou.

    The "how-to" videos are immensly helpful to us newbies. Without them people like me would still be using Access vba for all our business applications. So thankyou again for helping us harness the benefits of .net.

  • I wrote a code in VB.Net 2005 which allow me from a textbox when double click to open a datagrid and when i click on a row in the data grid, the data in the selected row first column will appear in a textbox. then the form with the datagrid will close and it will transfer this data in to the first form in the textbox where i double click. the prob is that when i do this the first time, it doesnt work. i have to do it a second time then it does... y is that so???

Page 1 of 3 (38 items) 123