When using simple list binding
(see Simple List Binding) the simple bound control needs to synchronize
to the "current item" of its data source. In order to do this, the binding
needs to get the "Current" item for the CurrencyManager associated with the
binding. The binding gets a CurrencyManager for its data source through its
Control’s "BindingContext" property (a Control typically gets its
BindingContext from the parent Form). The "BindingContext" is a per-Form cache
of CurrencyManagers (more precisely, BindingContext is a cache of
BindingManagerBase instances and BindingManagerBase is the base class of
CurrencyManager).
As an example, suppose a developer
had a list of Customers bound to a DataGrid control (e.g. DataGrid.DataSource
is set to a list of Customers). In addition, the developer had a simple
control, such as a TextBox, bound to the same list (e.g. TextBox.Text is bound
to the property "Name" on the Customer list using Simple List Binding).
When clicking on an item in the DataGrid, the DataGrid will make the clicked
item the currently selected item. The DataGrid does this by first asking its
BindingContext for the BindingManagerBase (CurrencyManager) of its data source
(list). The BindingContext returns the cached BindingManagerBase (and creates
one if it doesn’t exit). The DataGrid will use the BindingManagerBase API to
change the "Current" item (it does this by setting the CurrencyManager Position
property). When a simple binding is constructed binding it will get the BindingManagerBase
(CurrencyManager) associated with its data source. It will listen to change
events on the BindingManagerBase and synchronize updates to its bound property (e.g.
"Text" property) with updates to the BindingManagerBase "Current" item. The
key to this working correctly is that both the DataGrid and the TextBox need to
be using the same CurrencyManager (BindingManagerBase). If they are using
different CurrencyManagers, then the simple bound property will not correctly
update when the DataGrid item changes.
As previously mentioned, Controls
(and developers) can get a BindingManagerBase for a data source using a
Controls BindingContext. When requesting a BindingManagerBase from the
BindingContext, the BindingContext will first look in its cache for the
requested BindingManagerBase. If the BindingManagerBase doesn’t exist in the
cache, then the BindingContext will create and return a new one (and add it to
the cache). The BindingContext is typically global per form (child controls delegate
to their parents BindingContext) so BindingManagerBases (CurrencyManagers) are
typically shared across all Controls on a Form. The BindingContext has two
forms:
/* Get a BindingManagerBase for the given data source */
bmb = this.BindingContext[dataTable];
/* Get a BindingManagerBase for the given data source and data member */
bmb = this.BindingContext[dataSet, "Numbers"];
The first form is commonly used
when getting a BindingManagerBase for a list such as an ADO.NET DataTable. The
second form is used to get a BindingManagerBase for a parent data source that
has child lists (e.g. DataSet with child DataTables). One of the most
confusing aspects of BindingContext is using the different forms to specify the
same data source you result in two different BindingManagerBases instances.
This is by far and away the most common reason Controls bound to the same data
source don’t synchronize (Controls use BindingContext to get a
BindingManagerBase).
Sample: Control Synchronization
(VS 2005) (VS Projects: CurrencyAndBindingContext and DataBinding Intro)
/* Create a DataSet with 1 DataTable */
DataSet dataSet = new DataSet();
DataTable dataTable = dataSet.Tables.Add("Numbers");
dataTable.Columns.Add("ID", typeof(int));
dataTable.Columns.Add("Name", typeof(string));
dataTable.Rows.Add(0, "Zero");
dataTable.Rows.Add(1, "One");
/*******************************************************************
* Bind the first DataGridView and TextBox to the "Numbers" table in
* dataSet. The DataGridView will use BindingContext to get a
* CurrencyManager for the data source. DataGridView1 will use
* the following form of BindingContext:
*
* bmb = BindingContext[dataSet, "Numbers"];
*
* The textBox1’s Text Binding will also get a BindingManagerBase
* and will use the following BindingContext form:
*
* bmb = BindingContext[dataSet, "Number"];
*
* Therefore both dataGridView1 and textBox1 will share the same
* BindingManagerBase (CurrencyManager).
*******************************************************************/
this.dataGridView1.DataSource = dataSet;
this.dataGridView1.DataMember = "Numbers";
this.textBox1.DataBindings.Add("Text", dataSet, "Numbers.Name", true);
/*******************************************************************
* The variable "dataTable" contains the "Numbers" table. Although
* the above DataGridView and TextBox bound to this table using
* "DataSource" and "DataMember" form, they could have bound to the
* same Table (and data) by binding directly to "dataTable" as shown
* below. When doing this, DataGridView2 will use the following form
* of BindingContext:
*
* bmb = BindingContext[dataTable];
*
* The textBox12’s Text Binding will use the following BindingContext
* form:
*
* bmb = BindingContext[dataTable];
*
* Therefore both dataGridView2 and textBox2 will share the same
* BindingManagerBase (CurrencyManager) however they will not
* share the same CurrencyManager since they used a different form
* to specify their bindings.
*******************************************************************/
this.dataGridView2.DataSource = dataTable;
this.textBox2.DataBindings.Add("Text", dataTable, "Name", true);