Tips on Related Data Binding and ComboBoxes

Published 25 April 07 10:30 AM

I've been noticing a lot of questions on the forums related to Winforms data binding and the ComboBox and I thought I'd post something up here to help people out.  In fact data binding, or what we call “Windows Forms over Data” is a huge, sometimes misunderstood, topic.  Because of this I’m putting together a “how-to” video series on a variety of topics in this area. Two of the videos are dedicated to data binding the ComboBox in a couple very common scenarios:

1. To display information from a lookup table and send the selected value into another table's field.

2. To display a list of parent table's records and use that as a filter to display related child records. For instance, as the user selects a record in the ComboBox, you want to display all the related child records in a grid.

The trick is setting up the data binding properly using the BindingSource. I wrote about how to set up related lists in this post in order to get this second scenario to work with BindingSources managing lists of objects. Here, I’ll put it into the context of using DataSets.

In the first case it's not necessary to set up a data relation in your DataSet between the lookup table and the table you're editing, but it doesn't hurt. In the second case it is necessary to create a relation between your parent and child tables. Let's take an example from our beloved Northwind.

First we’ll use the Data Source Configuration Wizard to create a DataSet with Regions and Territories.  In VS 2005, go to the Data menu and select “Show Data Sources” then select “Add New Data Source”.


If we take a look at the DataSet through the DataSet Designer, we see that these tables are related on RegionID:

In the first scenario we want to select a Region from the ComboBox and have that value populated into the Territorries record. In this case we set up the RegionBindingSource with the following properties:

Me.RegionBindingSource.DataSource = Me.NorthwindDataSet

Me.RegionBindingSource.DataMember = "Region"

Then you use set the BindingSource as the Datasource of the ComboBox and set the display member and value member properties:

Me.ComboBox1.DataSource = Me.RegionBindingSource

Me.ComboBox1.DisplayMember = "RegionDescription"

Me.ComboBox1.ValueMember = "RegionID"

These properties control what items are displayed in the ComboBox and what value is used when the user makes a selection. Now to get that value into the Territories table, we use the TerritoriesBindingSource when we create the binding to the RegionID:

Me.TerritoriesBindingSource.DataMember = "Territories"

Me.TerritoriesBindingSource.DataSource = Me.NorthwindDataSet


Dim
b As New System.Windows.Forms.Binding("SelectedValue", _

             Me.TerritoriesBindingSource, "RegionID", True)

 

Me.ComboBox1.DataBindings.Add(b)

Okay we're all set, right? Well... almost! You'll also need to call EndEdit on the TerritoriesBindingSource at some point in order to write the value back to the DataSet. If you’re using the designers to do this then it takes care of the code for you when you click save on the BindingNavigator’s save button. So depending on the style of your form you could do this from an "Update" button (similarly you could call CancelEdit from a Cancel button).

Private Sub TerritoriesBindingNavigatorSaveItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TerritoriesBindingNavigatorSaveItem.Click

 

    Me.Validate()

    Me.TerritoriesBindingSource.EndEdit()

    Me.TerritoriesTableAdapter.Update(Me.NorthwindDataSet.Territories)

 

End Sub

The cool thing about EndEdit/CancelEdit on the BindingSources is that they cancel or commit only the fields in which they have bindings for, where as the DataSet rows' AcceptChanges/RejectChanges works on the whole row regardless of the data bindings.

Now let's take our second scenario where we want to use the ComboBox as a row filter. In this case we have to have a relation set up between our parent and our child; in the example this is FK_Territories_Region in our DataSet. So the BindingSources in this case need to be set up so that they are related as well. If you use the Data Sources Window, make sure you are working with the Region and the related Territories, just like if you were creating a One-to-Many form.

So the BindingSources will be set up like so:

Me.RegionBindingSource.DataSource = Me.NorthwindDataSet

Me.RegionBindingSource.DataMember = "Region"

 

Me.TerritoriesBindingSource.DataSource = Me.RegionBindingSource

Me.TerritoriesBindingSource.DataMember = "FK_Territories_Region"

Notice that the main difference here is that the TerritoriesBindingSource’s DataSource property is set to the parent BindingSource, RegionBindingSource and its DataMember is the relation name. This sets up automatic filtering on the TerritoriesBindingSource as the position changes on the RegionBindingSource. Also notice how the BindingSources decouple the data from the actual controls, making it very easy to switch controls or change the data sources.
 
Now the ComboBox properties can then be set up just like the first example:

Me.ComboBox1.DataSource = Me.RegionBindingSource

Me.ComboBox1.DisplayMember = "RegionDescription"

Me.ComboBox1.ValueMember = "RegionID"

Technically we don't need to specify the ValueMember property this time because we're not writing it anywhere, but it doesn't hurt to specify it. Next all we need to set up is the DataSource property of the DataGridView setting it to the TerritoriesBindingSource.

Me.TerritoriesDataGridView.DataSource = Me.TerritoriesBindingSource

(By the way, all the code up to this point could all be written by the designers by using drag-and-drop from the Data Sources Window. You’ll see this in the videos ;-))

Okay we're all set, right? Well... almost! Unfortunately a ComboBox won't move the BindingSource’s position for you like list controls do (Grids, ListBoxes). So the trick is to simply set the position of the RegionBindingSource by handling the ComboBox's SelectedIndexChanged event. So we need to write some code:

Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged

     

    Me.RegionBindingSource.Position = Me.ComboBox1.SelectedIndex

End Sub

Because we have related BindingSources the grid will automatically filter its rows based on the selected parent row in the ComboBox.

And notice that this code would work with other controls as well, for instance, instead of a ComboBox we could have used a ListBox or have set up two related grids.

Because we’re using DataSets as the source of our data, the BindingSources are maintaining DataViews so you can easily access the current DataRowView and cast it to the Typed DataSets’s row for typed access to the fields. For instance it’s handy to be able to get the current row when working on your form. You can do this by adding a property to your form. Just make sure to check for Nothing because there may not be a selected row:

Public ReadOnly Property CurrentRegion() As NorthwindDataSet.RegionRow

    Get

        If Me.RegionBindingSource.Position > -1 Then

            Return CType(CType(Me.RegionBindingSource.Current, DataRowView).Row, NorthwindDataSet.RegionRow)

        Else

            Return Nothing

        End If

    End Get

End Property

We can use this property like so:

Dim region As NorthwindDataSet.RegionRow = Me.CurrentRegion

 

If region IsNot Nothing Then

    MsgBox("The selected Region is:" & region.RegionDescription)

End If

The BindingSources are your friends. They decouple the data from the actual controls, making it very easy to switch controls or change the data sources without affecting the bindings. Use the Data Sources Window and it will save you from writing 90% of your data binding code. Have fun and keep a look out for the Windows Forms over Data video series that will be published soon!

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

# JamieO said on April 25, 2007 10:01 PM:

As someone looking towards .NET from VFP, I can't believe that using lookup lists from data is still a "difficult" issue!

No matter how you look at it, .NET still has a long way to go with data handling!

# Beth Massi said on April 25, 2007 10:15 PM:

Hi Jamie,

I understand that this is a beginner topic but traditionally VB6 had very different support of data binding than VFP :-) so the questions come up in the forums because data binding in .NET is very different than VB6. And remember 90% of the code I showed is actually created by the designers, I just wanted to put it in the post so people could understand. What do you see as being difficult, specifically? Thanks for the input!

# JamieO said on April 30, 2007 9:04 PM:

I don't see it as being particularly difficult -  well it is easier in VFP that's for sure. I was commenting on the fact that you were seeing lots of questions regarding data binding and the combobox. I thought if there's lots of questions related to that, there's a lot of people out there finding data handling in .NET difficult.

A combobox lookup is one of the most basic and common elements out there I would have thought.

# jfnbpqriws said on June 18, 2007 4:28 AM:

Hello! Good Site! Thanks you! hzanrkhjvggq

# Eric T. said on June 18, 2007 3:04 PM:

Cool stuff, but how about two related comboboxes rather than a combobox and a grid?

Replace the grid in your second scenario with a combobox.  Changing the value of the parent does update the child (at least visually), but the child's SelectedIndexChanged event doesn't fire.

I want to do something based on the value of the child.  In order to this I need this event to fire or its SelectedValue to get updated.  No matter what I do, I can't seem to get either to happen.  Any ideas?

Huge thanks if you've got any.

# Joseph said on July 5, 2007 12:00 PM:

How come when using the you select something in the dropdown, you then can not see it in the list.

What could i be doing wrong to cause this?

dropdown example:

item1

item2

item3

then when i select item 3 and look at the dropdown again it will show

item3

item2

item3

--Joseph

# Beth Massi said on July 5, 2007 8:25 PM:

Hi Eric,

You can handle the CurrentChanged event on the child's BindingSource in order to react to the filter changing.

-B

# Beth Massi said on July 5, 2007 8:27 PM:

Hi Joseph,

I'm not sure I understand your problem. When you select an option in a combobox and then drop it down again, it will be positioned on that selection but you can still scroll to the other items in the list.

-B

# Joseph said on July 11, 2007 9:32 AM:

ok. I found out the problem i had. here is the problem now. hard to explain, but i will try.

I have a main form, that shows all job orders. you click on a job order and it takes you to view the details of that job order.

On the details view, all information comes up correct, there is a binding navigator that shows you how many jobs for that customer, and you can even navigate through them all.

When i add dropdowns for the lookup items. they default view the first item in the dropdown, they do not show what is in the database until i use the binding navigator to go up a record then back down a record. Then the dropdown displays correctly.

Any ideas?

# Goran said on August 10, 2007 7:05 AM:

Dear Beth,

I have 2 forms and on each form datagridview and when I press enter on grid2 that pick data from rows and put them in grid 1.

The form2 is like search form!

# Pete said on January 22, 2008 6:13 AM:

Hi Beth,

This is a great Tutorial for someone like me coming back to VB after a couple of versions away.  How would you feel about re-doing it within the context of LINQ and Visual Studio 2008?

Regards,

Pete

http://www.channelcomputing.co.uk

# Barry said on January 23, 2008 2:27 PM:

Hello,

I have a combobox where the DataSource is bound to a datatable and the SelectedValue is bound to a class object property. The class object is not a list, but a simple business object. The combobox will not update the class objects property no matter what I do, unless I set the property value in the SelectionChangeCommitted event. The class object bindings work on text boxes, but not with the combobox SelectedValue. I'm thinking the class is missing the implementation of some interface, but it does work with textboxes, etc.

Thanks

Barry

# Beth Massi - Sharing the goodness that is VB said on February 6, 2008 5:07 PM:

In a previous post I showed how to set up related data binding using ComboBoxes against DataSets and

# Noticias externas said on February 6, 2008 6:15 PM:

In a previous post I showed how to set up related data binding using ComboBoxes against DataSets and

# Hot Topics said on February 10, 2008 7:18 PM:

Two recent LINQ databinding with ComboBoxes posts from Beth Massi of the Visual Basic team. Creating

# Kate said on April 25, 2008 3:30 AM:

Hi Beth,

I'm actually working in c# rather than VB but this is the best tutorial I've seen on what I'm trying to achieve. I have a couple of questions though...

My scenario is that I have two comboboxes (Countries and Offices), some text fields (contain info about the currently selected office) and a datagridview (contains opening time info about the currently selected office).

I have not set it up to use DataSets at this point, as I wasn't sure if it would be able to do what I want. I have a DataTable (contains CountryCode and Country) bound to the Countries combobox and just before I bind it, I programmatically add another row at the top for "All Countries". I have a SelectedIndexChanged event handler on Countries that sets a filter on the bindingsource for the Offices combobox or if the "All Countries" option is selected, it clears the Filter. The down side is that when the filter is applied, I haven't managed to work out a way to get the first displayed item in the Office combobox to be selected and as such, all the info shown in the textboxes (and the datagridview) is for the office that was previously selected rather than the currently displayed office. Obviously, this is not good and so I was thinking of switching it over to use a DataSet in order to get the select working but if I do that, can I get it to display all offices in the Combobox when "All Countries" is selected rather than no offices (as obviously no offices are associated with "All Countries")?

Thanks,

Kate.

# kostas said on May 3, 2008 9:35 AM:

Hi Beth,

You are really something. I think you are the most popular vb programmer. Good job. So, question: Is there a way that i can use lookup on combo which is in a datarepeater??? This would save me from a lot of trouble

Thanks in advanced.

Kostas

# Beth Massi said on May 6, 2008 2:13 PM:

Hi Kate,

If you set up the binding like in the second scenario in the above example then when you add a new row it looks at the RegionID to determine what to display in the combobox. If the RegionID is System.DBNull by default then you need to make sure the "blank" row you are adding to the combobox's datasouce has a RegionID of System.DBNull as well.

HTH,

-B

# Beth Massi said on May 6, 2008 2:14 PM:

Hi Kostas,

This is not directly supported but check out this post for a possible solution: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3097892&SiteID=1

HTH,

-B

# Lyle Chubb said on May 22, 2008 2:08 PM:

I really enjoyed reading this article, hoping it would give me a clue to my dilemma.   It is a start, but I'm not any closer.  Here is the issue:  I am giving users a form to enter a new customer record.  Once they complete the record (it is validated on that form), they hit a Record button that appends the record to the underlying dataset table.  The combo box on the order form is then supposed to be updated with the CustomerID and the CustomerName they have just added, but no such luck.  I have verified that the record is making it into the SQL table, I have verified that the new record is getting into the BindingSource for the combo box, but I cannot figure out how to get that value to appear in the drop-down list!  At this point I am assuming it requires the sacrifice of two or more small animals....

Here is the code I am using to update the record.  Any advice or suggestions you can offer would be GREATLY appreciated!  Thanks!

SaveToServer(MyCust)

Me.taShipTo.Fill _(Me.FabDrawDataSet.Special_Customers)

bsShipTo.ResetBindings(False)

Lyle

# Lyle Chubb said on May 22, 2008 2:33 PM:

I thought it might be helpful for you to see the SaveToServer(MyCust) procedure, in case I am missing something in that block....

   Private Sub SaveToServer(ByVal MyCust As Customer)

       Try

           Dim custRow As FabDrawDataSet.Special_CustomersRow

           custRow = Me.FabDrawDataSet.Special_Customers.NewSpecial_CustomersRow

           custRow("CustName") = MyCust.CustomerName

           custRow("Addr1") = MyCust.Address1

           custRow("Addr2") = MyCust.Address2

           custRow("CustCity") = MyCust.City

           custRow("CustState") = MyCust.State

           custRow("CustZip") = MyCust.ZipCode

           custRow("CustPhone") = MyCust.Phone

           custRow("CustFax") = MyCust.Fax

           custRow("Contact") = MyCust.Contact

           custRow("ContactPhone") = MyCust.ContactPhone

           Me.FabDrawDataSet.Special_Customers.Rows.Add(custRow)

           Dim newRecords As FabDrawDataSet.Special_CustomersDataTable = CType(Me.FabDrawDataSet.Special_Customers.GetChanges(Data.DataRowState.Added), FabDrawDataSet.Special_CustomersDataTable)

           If newRecords IsNot Nothing Then

               taShipTo.Update(newRecords)

               newRecords.Dispose()

           End If

           Me.FabDrawDataSet.Special_Customers.AcceptChanges()

       Catch ex As Exception

           MessageBox.Show(ex.Message, "Saving Customer Record", MessageBoxButtons.OK)

       End Try

   End Sub

# Huw Bristow said on September 25, 2008 9:55 AM:

Do you know how to get around the problems with displaymember and inherited objects?

I have interface 2

  public interface Interface2:Interface1

   {

       string ShortDescription{ get; set;}

   }

and interface 1

   public interface Interface1

   {

       string LongDescription { get; set; }

   }

I am then binding to a list of objects of type class2 (which implements interface2) and inherits class1 (which implements interface 1)

 List<Interface2> item = new List<Interface2>();

           for(int i = 0; i < 10; i++)

           {

               item.Add(new Class2( string.Format("Short {0}", i), string.Format("Item {0}", i)));

           }

           comboBox1.DataSource = item;

           comboBox1.DisplayMember = "ShortDescription";

Short description works fine, change to

comboBox1.DisplayMember = "LongDescription";

it fails to display the correct description in the list at just shows the default tostring

thanks

Huw

# essam said on October 3, 2008 10:06 PM:

I think it would be great to have a video (screencast ) version of this atricle

# Jack said on October 24, 2008 4:39 PM:

Beth,

This is an AWESOME article, especially for somebody coming from a strong vba/vb6 background, who is used to data application dev in MS Access, and trying to get adapted to the .NET paradigm. This really cleared up alot of questions for me about working with bindings in .NET, and getting them to perform similar to Access. Thanks alot for including your code too. Kudos!

# Greg said on December 15, 2008 11:56 AM:

Thanks for the help with figuring out how to sync the binding source in the SelectedIndexChanged event.

# Beth Massi - Sharing the goodness that is VB said on May 1, 2009 1:18 PM:

It’s extremely common to have to hook up lookup tables on your data entry forms in order to populate

# Brad said on July 2, 2009 1:50 PM:

Beth;

Either I am missing a step or there is something I don't understand here.  

Right below the dataset diagram you talk about setting up the RegionBindingSource with the properties:

Me.RegionBindingSource........

Where is that done if the forms haven't been created yet as you show on next page?

Where do you set up these properties?  Did I miss something in a previous lesson?  I'm new to VB and trying to get my head around all of this.  

Thanks

Brad

# Beth Massi said on July 2, 2009 8:40 PM:

Hi Brad,

Check out this video which should help explain how to create a data form and set up the BindingSources: http://msdn.microsoft.com/en-us/vbasic/cc138241.aspx

HTH,

-B

# David said on July 7, 2009 11:42 PM:

Hi Beth,

I'd like a combobox to display the data from a database but the dropdownlist only some predefine elements. I have not figure out how to do that because if I bound the combobox to the database it displays the data fine but the dropdownlist is bound to the database as well.

any suggestions?

# Christopher said on July 16, 2009 8:30 AM:

Hello Beth,

First Thanks for all the great help. Your videos are a great reference for me. I have a problem when using multiple comboboxes in a datagridview where the first combobox selection is used to filter the second comboboxes selections and the first and second combo boxes are used to filter the third comboboxes selections.

An example would be selecting general ledger account numbers, where the account numbers are made up of 3 fields. Account Number, Sub Account Number and Cost Center.

When adding records to the detail check dispersement file each record requires a valid GL account. In the Datagridview for entering the check detail records I can get everything to work I can even get the filtering for the 3 comboboxes to work. The problem is when adding or changing  records and moving from one record to a different record. The Datagridview will crash with an error.

Here is a little more detail:

GL account records:

Acct#  Sub#  CC      Description

3001    001                account 1

3001    002                account 2

3001    003                account 3

3002                          account 4

3003    004    401      account 5

3003    005                account 6

When adding records to the check detail datagridview everything works as long as I don’t try and go back to a previous record. If for example I added the first record and used “account 2” and for the second record I used “account 5”. After adding the second record and try to go back to the first record the datagridview crashes with an error. The problem is when you start to enter the first record from the second record the datagridview uses account 5’s Acct#(3003) to filter the Sub# combobox on the first line. As you can see the first detail line has Account 2 Acct#(3001) that does not have a Sub# of  “004”, so the filter/relation fails and the datagridview fails.

I have Researched this problem and have not found a viable solution as of yet. If you can help point me in the right direction it would be much appreciated.

Thanks in advance.

# Beth Massi said on July 16, 2009 1:25 PM:

Hi Christopher -

What's the error and stack trace? Have you tried debugging? You're going to need to make sure that the value that the row has in its field that the combobox value is bound to exists in the list and you aren't filtering that out.

You may also want to ask your question in the forums where many Microsoft customer support and other people are there to help troubleshoot.

http://social.msdn.microsoft.com/Forums/en-US/winformsdatacontrols/threads

-B

# John said on July 22, 2009 7:07 PM:

Hi Beth

Firstly thanks for your superb videos. For a newby like me they have helped me progress far quicker than would otherwise have been the case.

Been trying out your example of using a lookup dataset with a combo box in a datagrid and everything works great until I close the form when I get the following error:

The following exception occurred in the DataGridView:

System.ArgumentException: DataGridViewComboBoxCell value is not valid

To replace this default dialog please handle the DataError event.

I can add, delete and edit to my heart's content and save all the changes without a problem until I close the form.

There is no code other than that generated by the designer and curiously I have no problems using lookups on a second laptop where it all works flawlessly. Unfortunately my 12" touch screen tablet is the one I'm trying to write an app for and also the one throwing a wobbler!

Any ideas?

Thanks again for sharing your knowledge and time. I listen to you on your videos so often it's almost like I know you :-)

# Beth Massi said on July 31, 2009 4:37 PM:

Hi John,

I'm not really sure why that's happening sometimes. You may want to just try adding a handler to the Form's Close event and setting the BindingSource.DataSource property to Nothing before the form closes.

Good luck,

-B

# DanD said on October 15, 2009 6:43 AM:

I encountered the same problem as Joseph said on July 5, 2007, I also found the fix and the cause (in this order).

Problem description: Using VB 2008, I followed similar steps to the first scenario (using the designer instead of writing text). As a result, when navigating the Territories table the values in the Region ComboBox started duplicating. The number of elements in the drop down remained but they were not distinct anymore (eg: Western, Western, Northern, Western). After some debugging I found out that the values in the RegionDescription column of the Region table were actually being modified when I navigated.

The fix: In the properties for the ComboBox go to (DataBindings) and click “...” at the end of the (Advanced) row. In the “Formatting and Advanced Binding” you will see – under Common properties - that you have 2(?!) Bindings: one for the SelectedValue property and another for the Text property. Select the Text property and set the Binding to None.

The cause: When I stared I didn't drag a “clean” ComboBox from the Toolbox, I dragged the RegionDescription column from the  DataSources window (this already created a binding between the Text property and the RegionDescription column). I then used the smart tags, enabled “Use data bound items” and added another binding (the correct one). Result: 2 bindings and the strange behavior.

[Note: I actually encountered the problem on my own DataSet, not a Northwind based one]

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