Using Data Across Multiple Windows Forms

Published 01 October 07 11:11 AM

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!

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Techy News Blog » Using Data Across Multiple Windows Forms said on October 1, 2007 3:58 PM:

PingBack from http://www.artofbam.com/wordpress/?p=4343

# Warren said on October 4, 2007 3:22 AM:

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

# Beth Massi said on October 11, 2007 2:07 PM:

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

# ROy said on November 7, 2007 3:00 AM:

Great article. Just what I was looking for.

# Celestine said on November 19, 2007 11:58 AM:

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.

# Charlie said on December 4, 2007 12:04 PM:

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?

# Beth Massi said on December 4, 2007 6:32 PM:

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

# Beth Massi said on December 4, 2007 6:34 PM:

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

# Arsalan Tamiz said on March 27, 2008 5:30 AM:

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

# George Tekelis said on May 3, 2008 8:58 AM:

Hi Beth!!

Thanks for your great articles and help!!!

# Laurent SANCHEZ said on May 11, 2008 6:18 AM:

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

Good job!

Laurent

# Martin Ward said on June 22, 2008 5:25 PM:

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.

# Beth Massi said on June 25, 2008 12:27 PM:

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

# Martin Ward said on June 25, 2008 3:47 PM:

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.

# Shellen said on July 21, 2008 12:27 AM:

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???

# Shellen said on July 21, 2008 12:32 AM:

can someone help me out and rep me on my email shellen85@yahoo.com

# Shellen said on July 21, 2008 12:32 AM:

can someone help me out and rep me on my email shellen85@yahoo.com

# Beth Massi said on July 21, 2008 11:36 AM:

Hi Shellen,

Check the Position property of the BindingSource and make sure it is correct the first time.

HTH,

-B

# CrashDaddy said on October 16, 2008 12:10 PM:

Hi, Beth, thanks for the article!  I've been searching and looking and googling and scratching my brain trying to solve a problem I'm having, and maybe you can help.

How oh how would I be able to use the same form to change data that is stored in *multiple* datagridviews in a tabbed control?  

In my production environment the users all have touch screens and there are several datagridviews in a tabbed control on the main form. All of the datagridviews interact with the same database, but different tables.  

I made a nifty little numberpad for them to enter the data, but I don't know how to make it remember from which datagridview cellclick event it is being called.

Wah.

Maybe I'm just approaching the whole thing wrong!  Can you help me or point me in the right direction.  I know I could make six different numberpads, but I also figure there's a much more elegant solution.

# Larry Freedman, Goldens Bridge NY said on November 20, 2008 4:12 PM:

I came across this article on binding which I found very informative. However what I want to do is to have the Product Detail (Form2) change the entry when the product changes in the main form (Form1). Have you written anything that sheds light on how to do that?

# Waner Michaud said on March 27, 2009 8:41 AM:

Can anyone please help me.  Using Article Data across multiple form.  I want to add new complainant and pass complainant_ID to a Registration form and add  an Offender to that registration Form.  when done, we have on Registration, Registration_ID, Complainant_ID, Offender_ID .

# Waner Michaud said on March 30, 2009 9:59 AM:

How can i create a new customer and push the customerID into the product table

thanks

# Beth Massi said on March 31, 2009 4:11 PM:

Hi Larry,

You could do this a couple ways. One way would be to share the ProductBindingSource across the forms, or handle the PositionChanged of the main form's bindingsource. But a cleaner way in this example would be to just raise an event from the main form that passed the ProductID as the event argument. Then the detail form could just handle the event and set the Filter again.

HTH,

-B

# Beth Massi said on March 31, 2009 4:35 PM:

Hi Waner,

I'm not sure what you mean, there is no customerID in the example above. But you can create a new product by clicking the New button in the example. If you want to add a new Category then you sould just call AddNew on the CategoryBindingSource.

Here's a couple videos that may help you understand these data objects better:

http://msdn.microsoft.com/en-us/vbasic/bb725824.aspx

http://msdn.microsoft.com/en-us/vbasic/cc138241.aspx

HTH,

-B

# Waner Michaud said on March 31, 2009 5:24 PM:

HI Beth,

Thanks for reply..i meant categoryid.

category is the parent table and product details is the child.. when i go add, new category creates and i want to create a new product for this new category at the same time.

I have posted full explaination for what I want to accomplish

Thanks

# Waner Michaud said on April 1, 2009 8:49 AM:

Hi Beth,

How can I use this example using two parent tables feeding into one child table..

for example, Complainant table and Offender table as parent tables and  create a registration as a child table.

# Waner Michaud said on April 2, 2009 3:50 PM:

HI,

Can anyone help me.. if I add an entry on a parent table. can the PK_ID parse to a child table as FK on the child table simultaneously..If that's so how..

what i want to do is that I have two parent tables, i want to create a registration on those two PK key on the registration table.

Guide me if possible..

Thanks

# Beth Massi said on April 2, 2009 4:21 PM:

Hi Waner,

If you have your BindingSources set up properly then when you add a new child it will automatically add the FK for you. In the example above if you want to add a new product "at the same time" when a new category is added then just call:

CategoryBindingSource.AddNew

CategoryBindingSource.EndEdit

ProductBindingSource.AddNew

ProductBindingSource.EndEdit

Note that a category must exist before a product can be added and that the above code assumes the data rows are valid before calling EndEdit.

You may want to ask your question in the forums, there are a lot more people there including MSFT tech support that can help. http://social.msdn.microsoft.com/Forums/en-US/category/visualbasic

Please also read up on the BindingSource as well as how datasets work. You'll need to configure them correctly in order for them to save hierarchical data like I showed in this video:

http://msdn.microsoft.com/en-us/vbasic/cc138241.aspx

HTH,

-B

# Dennis Smits said on April 8, 2009 4:30 PM:

Hi Beth,

I really love your How-Do-I videos.

I came from a VB6 environment and now we are building our apps in C# .NET (just recently and it was a business justification to use C# over VB.NET).

thanks for your help and examples.

Dennis Smits (The Netherlands)

# Waner Michaud said on April 20, 2009 9:12 AM:

Hi Beth,

I use this technique to parse the values from  formA to load another formB...These two forms are from the same table. same dataset.

When save formA, it saves correctly, when click OK on formB which is a save function, nothing happens on the database, but I click save on formA, whatever I update on formB save on the database..

i need when I click on OK on formB, to save/update the database not when go back to FormA for it to save..

Can you guide me on what to do

Thanks

# Sharf said on September 24, 2009 8:18 AM:

Hi Beth,

I'm developing ERP project for Furniture company. I have 5 Forms in that project. They are,

1) Warehouse 2) Stock Maintanence 3) Purchase 4) Sales and 5) Report Form

My question is,

1)How can prevent CONCURRENCY PROBLEM and Unit-In-Stock is upto date?

2)And also, If I take report it is always slow. even single transaction the report is taking time.

3)How can I improve the performance?

How can I design DAL in this case?

Can you provide links or samples...please?

Thanks,

Sharf

Leave a Comment

(required) 
(optional)
(required) 

  
Enter Code Here: Required

About Beth Massi

Beth is a Program Manager on the Visual Studio Community Team at Microsoft and is responsible for producing and managing content for business application developers, driving community features and team participation onto MSDN Developer Centers (http://msdn.com), and helping make Visual Studio one of the best developer tools in the world. She also produces regular content on her blog (http://blogs.msdn.com/bethmassi), Channel 9, and a variety of other developer sites and magazines. As a community champion and a long-time member of the Microsoft developer community she also helps with the San Francisco East Bay .NET user group and is a frequent speaker at various software development events. Before Microsoft, she was a Senior Architect at a health care software product company and a Microsoft Solutions Architect MVP. Over the last decade she has worked on distributed applications and frameworks, web and Windows-based applications using Microsoft development tools in a variety of businesses. She loves teaching, hiking, mountain biking, and driving really fast.

This Blog

Syndication

Page view tracker