Synchronization Issues in Windows Forms Data Binding

Feb 27, 11:00 pm

Article Author: Abu Uddin
.NET 3.5 Books

Introduction


A couple of months ago I had a requirement for a project to use two combo boxes on a form bound to the same data source. Even though they were bound to the same data source, I needed them to behave independently of each other. OK, fine, I placed the combo boxes on the form, I bound them to the data source. But to my wonder, they were automatically synchronized with each other. That is, a selection change in one combo box was automatically reflected by the other. Why was that? I tried to "google" the problem, but none of the solutions satisfied me. The best among them was an article written by Dino Esposito on the Infragistics web site, in it Dino is basically using a trick to make the same data source appear as two different data sources.



cboEmp1.DataSource = data.Tables("Employees")
cboEmp1.DisplayMember = "lastname"
cboEmp1.ValueMember = "employeeid"
cboEmp2.DataSource = data
cboEmp2.DisplayMember = "Employees.lastname"
cboEmp2.ValueMember = "Employees.employeeid"


If you look at the example that he uses in his article, you will see that he is binding one ComboBox to a DataSource which is a table named Employees inside a DataSet called data. Then he is telling the ComboBox to display the field lastname. After that a second ComboBox is bound to the same DataSet (but this time only the DataSet, NOT the table inside, is acting as the DataSource). And then DisplayMember and ValueMember are set to Employees.lastname. This is really a nice trick. Though he is displaying the same data (the employee’s last name) is in the ComboBoxes, they are actually using different DataSources (one is the DataSet itself and the other is a table inside the DataSource).


Though this solution will work fine, one immediate problem if you want to make three (or even more) controls bound to the same data source, this trick won’t help you. Also this code might confuse a future reader of your code.


But of course, it gave me some insight to dig deeper into the way that Windows forms data binding works. In this article I will develop an application to solve the synchronization issue the .NET way. By default in a form, for each data source that is bound to one or more controls, there is exactly one BindingManagerBase object. This object is actually responsible for maintaining the synchronization between the data source and all the controls that are bound to it. You will see that by using different container controls (such as panel or frame) with different BindingContext for each separate view of the same data source, you can solve this problem.


System Requirements


To run the code for this sample you should have:


  • The .NET Framework version 1.x or version 2.0

  • VS.NET 2003 or VS 2005 (Express Edition or above)

Installing and Compiling the Sample Code


The sample code was written in C# but the concept will work in any .NET language. There is also an accompanying MS Access .mdb file (db.mdb) which you will need to move to the folder in which the compiled executable is generated – normally the /bin/debug folder. Two different sets of source codes are provided. One is for VS.NET 2003 and the other is for VS 2005.


For VS.NET 2003


In order to install the sample code just unzip the zip file and open the solution file in the folder Source 2003 with VS.NET 2003.


For VS.NET 2005


In order to install the sample code just unzip the zip file and open the solution file in the folder Source 2005 with VS 2005.


The Sample Application


The sample application’s main form is a simple form with four buttons in it as seen in Figure 1.



Figure 1. Main form of the sample application


Clicking the Default Data Binding button shows a form which reproduces the problem that I am trying to solve. In this form the user can enter their Shipping Address and Billing address. It will show a form similar to that shown in Figure 2, which will have two different combo boxes bound to the same data source – this displays the country list to choose a country for the Billing Address and another Country for Shipping Address. The country list is taken from a list of countries, stored as table called Countries in the database.



Figure 2. A form displaying the default data binding behavior


If you try to change the country name for a Billing Address or Shipping Address the other combo box will also change. We definitely don’t want this, as it is quite possible a user can have their billing and shipping address located in two different countries.


Clicking the Independant Data Binding button on the main form will show another form which does not have the synchronization problem. As we can see in Figure 3 this time the combo boxes will be able to act independently of each other. We will do this by placing the shipping and billing controls in two different panels and using different BindingContext for each of them. This allows you to change the country name in one combo box but the other combo box is not affected.



Figure 3. Independently acting views of the same data


This solution also works with more than two controls bound to same DataSource. If you click the Independent Three Combos button from the main form it will show the form in Figure 4. As you can see here all the three combo boxes can now act independently of each other.



Figure 4. Independently acting combo boxes


Clicking the Synchronization across forms button on the main form shows two different forms as in Figure 5 This is an example of how to maintain synchronization across different forms. Each of these two forms contains a combo box bound to the Countries table. You will see just by sharing its BindingContext, I was able to synchronize these two combo boxes, even though they are on two different forms.



Figure 5. Maintaining synchronization across different forms


Database Design


Since in this article I only need to demonstrate the synchronization issue for a single data source, it is enough for this application to use a single table with a single field that contains a list of country names. Hence, I created an Access database named db.mdb which contains a single table named Countries. This table contains a single field called Countries. As mentioned earlier the table has been populated with some country names.


Windows Forms Data Binding Concepts


Before we dive directly into the code, let’s have a brief look at how data binding works. This will help you understand the problem and solution better.


A Windows control can have many properties which can be bound to data sources. Each of these bindable properties has a Binding object associated with it. The BindingManagerBase class manages all the Binding objects that are bound to the same DataSource and DataMember. But since this BindingManagerBase class is abstract, it cannot be instantiated directly. Two classes that implement this class are CurrencyManager and PropertyManager. So, depending on the binding situation BindingManagerBase will be either a CurrencyManager or PropertyManager. When the DataSource is an object which returns a list of objects, it will be a CurrencyManager. But when the DataSource is an object which returns a single property, it will be a PropertyManager. Need an example? OK, for instance, when the DataSource is a DataTable (or anything which implements IList or IBindingList), it will be a CurrencyManager that manages all the Binding objects associated with this DataSource. On the other hand, if the DataSource is a text box, it will be a PropertyManager.


Now that we have the BindingManagerBase managing all the binding objects associated with a data source, we will need something which manages all the BindingManagerBase objects for a single form. Well, that’s the job of BindingContext. Each Windows form has a BindingContext property, which returns the collection of BindingManagerBase objects associated with that form.


So, what is causing the problem of auto sync in the data binding that we saw in Figure 1. Looking at Figure 6 may help shed some light.



Figure 6. There is only one BindingContext object for a Windows form


As I mentioned previously, each form has exactly one BindingContext. Since both the combo boxes are bound to the same DataSource, there is only one CurrencyManager for both of them. When you change selections in one combo box, it changes the pointer in the CurrencyManager, and when the pointer changes, CurrencyManager notifies all the controls that are bound to the same data source its managing, so that they can refresh their data. This is causing the other combo box to change automatically to whichever has been selected.


Now, you might ask, if a Windows form has exactly one BindingContext, how am I going to unsync the combo boxes that are bound to same data source? Well, not only a form, but any other Windows container control has its own BindingContext too. By default these container controls share the BindingContext of the parent control. So, if I place two different panels on the form and then use a new BindingContext for one of the panels, there will be two BindingContext objects, each managing its own data sources differently. Now I can put each of the combo boxes on a different panels, and voila, I have an independently scrolling Country list as shown in the design in Figure 7.



Figure 7. Different container controls can be used to solve the synchronization problem


The Code


For the sample application, first create a Visual C# Windows Application project in Visual Studio.NET. Place four buttons on the default form as in the Figure 1. Give them the following names: btnDefault, btnIndependent, btnIndependentThree and btnSyncForm.


The Auto Synchronization Problem


First I will show you the synchronization problem in an example. For this create another form (name it frmDefaultBinding) that looks like Figure 2. Rename the combo boxes to cmbBillingCountry and cmbShippingCountry. Since you will be using an Access database, write the following using statements at the beginning of your code for frmDefaultBinding.



using System.Data;
using System.Data.OleDb;


Declare the following variables as the instance variable of class frmDefaultBinding:



private OleDbConnection oleDbConnection1;
private OleDbDataAdapter oleDbDataAdapter1;
private DataSet ds;


Now in the constructor of the class create the connection to connect to the Access database. Then create an OleDbDataAdapter and OleDbCommand (to get the list of countries). Attach the OleDbCommand to the OleDbDataAdapter with the SelectCommand property:



public frmDefaultBinding()
{ this.oleDbConnection1 = new OleDbConnection(); this.oleDbConnection1.ConnectionString = &quot;Data Source=&quot;&quot;db.mdb&quot;&quot;; Provider=&quot;&quot;Microsoft.Jet.OLEDB.4.0&quot;&quot;;&quot;; this.oleDbDataAdapter1 = new OleDbDataAdapter(); OleDbCommand cmdSelect = new OleDbCommand(&quot;SELECT * FROM Countries&quot;, oleDbConnection1); this.oleDbDataAdapter1.SelectCommand = cmdSelect; ds= new DataSet(); InitializeComponent(); <!-- @START —>


In the Load event of the form you will fill the DataSet and bind the combo boxes to the countries table using the DataSource and DisplayMember property.



private void frmDefaultBinding_Load(object sender, System.EventArgs e)
{ oleDbDataAdapter1.Fill(ds,"Countries"); cmbBillingCountry.DataSource=ds.Tables["Countries"]; cmbBillingCountry.DisplayMember="Country"; cmbShippingCountry.DataSource=ds.Tables["Countries"]; cmbShippingCountry.DisplayMember="Country";
}


In the wizard-generated Dispose() method add the following lines of codes:



protected override void Dispose( bool disposing )
{ if( disposing ) { if(components != null) { components.Dispose(); } oleDbDataAdapter1.Dispose(); oleDbConnection1.Close(); oleDbConnection1.Dispose(); } base.Dispose( disposing );
}


In the click event of the btnDefault button on your main form add the code to display the frmDefaultBinding form:



private void btnDefault_Click(object sender, System.EventArgs e)
{ frmDefaultBinding frmDefault= new frmDefaultBinding(); using (frmDefault) //dispose form after using { frmDefault.ShowDialog(this); }
}


If you run the application now and display the frmDefaultBinding form, you will see this has the synchronization problem (i.e., changing selection in one combo box reflects in the other). If you are getting a OleDbException, remember to place the database file in your application’s current directory (for example, if you are running in debug configuration place it in /bin/debug folder).


Next we will create another form which does not have the synchronization problem. Create another form named frmIndependentBinding. Place two panels on the form with the name panBilling and panShipping. Resize them so that they can accommodate the other controls that you will place inside them. Place the combo box cmbBillingCountry into panBilling and cmbShippingCountry into panShipping. We are not interested in the other controls that I show in Figure 3because only the combo boxes have the functionality associated with them, the rest of the controls are for decorative purposes only. So, place the rest of the controls as you like. The code for the frmIndependentBinding will be the same as frmDefaultBinding class (except for one line). The using statements, instance variables and the code inside the class constructor, Dispose() method will be exactly same. But the Load event handler of the form will look like this:



private void frmIndependentBinding_Load(object sender, System.EventArgs e)
{ oleDbDataAdapter1.Fill(ds,"Countries"); cmbBillingCountry.DataSource=ds.Tables["Countries"]; cmbBillingCountry.DisplayMember="Country"; cmbShippingCountry.DataSource=ds.Tables["Countries"]; cmbShippingCountry.DisplayMember="Country"; panShipping.BindingContext= new BindingContext();
}


The only change here from the frmDefaultBinding form is that it is telling the panShipping to use a new BindingContext rather than the one from its parent (frmIndependentBinding). The other panel (panBilling) will keep using its parent’s BindingContext. Any other bound control on the parent form will also use this context.


Now show the form whenever someone clicks the second button on the main form:



private void btnIndependent_Click(object sender, System.EventArgs e)
{ frmIndependentBinding frmIndependent= new frmIndependentBinding (); using (frmIndependent) { frmIndependent.ShowDialog(this); }
}


Run the application now and you will see that one line of code was enough to bring in the independent behavior of the combo boxes. You can change one combo box without affecting the other.


Managing Synchronization Across Forms


What if you want to the opposite of what we have done in previous section? You might want to maintain the synchronization even if the combo boxes (or whatever bound controls) are placed on different forms. You just need to share the BindingContext of one form with another to do that.


To do this, create another form named frmSync. Place a combo box (name it cboCountries) on the form frmSync.


Add the following using statement in the code for the form:



using System.Data;


Create a method called SetBinding() on the form’s class:



public void SetBinding(DataSet ds)
{ cboCountries.DataSource=ds.Tables["Countries"]; cboCountries.DisplayMember="Country";
}


This method binds the combo box to the Countries table of the DataSet that is passed as a parameter.


In the calling main form set up the Connection , Command, Adapter and DataSet like you did with frmDefaultBinding. Then on the Click event for the button of your main form add the following code:



oleDbDataAdapter1.Fill(ds,"Countries");
frmSync SyncForm1= new frmSync();;
SyncForm1.Show();
frmSync SyncForm2= new frmSync();
SyncForm2.Show();
//show the second form just below the first form
SyncForm2.Top=SyncForm1.Bottom;
//share the BindingContext
SyncForm2.BindingContext=SyncForm1.BindingContext;
SyncForm1.SetBinding(ds);
SyncForm2.SetBinding(ds);


The code above first fills the DataSet, then it instantiates and shows two different instances of SyncForm(SyncForm1 and SyncForm2). Next SyncForm2 is instructed to share the BindingContext of SyncForm1. Here, though it’s creating two different instances of the same form, this technique will work even with two different forms.


Now if you call the SetBinding() method on both the forms, passing the DataSet, you will see that both the combo boxes in the two forms are synchronized with each other.


Conclusion


In this article you have seen how to manage concurrency in Windows form data binding. With a very simple application, I explained some very important topics in regards to data binding for Windows forms in .NET. You also got an overview of how the data binding works in .NET and also saw how to use the BindingContext object to solve the synchronization problem encountered in the data binding. One example showed how to have independently scrolling view on same data source on the same form. Another example showed how you can manage controls synchronized even when they are on different forms.

Founders at Work

Commenting is closed for this article.