Introduction
Data binding is a mechanism present in Windows Forms and WPF. A simple data binding, the subject of this article, is an active connection between a property on the host object and a property on a data source object. The host object is typically a user interface Control
, and the data source object is frequently a business object being manipulated by the user interface. The connection ensures changes in one object are reflected in changes to the other object.
The data binding provided by Control
objects is quite useful: it avoids the need to provide code to populate the content of Control
objects from business objects, and to update the business objects as the user interacts with the Control
objects. However, the user interface is not the only area that could benefit from data binding. Frequently business objects are themselves convenience representations of other, definitive, objects, such as rows in a database. The business object in such cases must provide code to populate the business object from the database, and update the database as the business object changes. Having the business object itself host data bindings could avoid the need to provide such code.
This article shows how objects of any class can host data bindings, and provides an abstract
base class that incorporates the infrastructure required. A simple application demonstrates the use of the base class by implementing a chain of four objects updating one another.
Background
Simple data binding connects a specified property of a host object (generally an instance of the System.Windows.Forms.Control
class) with a specified property of a data source object. A data binding is created by adding a System.Windows.Forms.Binding
object to the System.Windows.Forms.ControlBindingsCollection
collection of bindings maintained by the host object. For Control
objects, this collection is returned by the DataBindings
property. Binding
objects are generally created through convenience methods provided by the ControlBindingsCollection
object, rather than using the new
operator. A typical Binding
creation might look like this:
NameTextbox.DataBindings.Add ("Text", _customer, "Value");
This binds the Text
property of the object NameTextbox
to the Value
property of the object _customer
. In this binding, NameTextbox
is the host and _customer
is the data source.
When the binding is first established, the specified property of the data source object is copied to the specified property of the host object. Thereafter, the specified properties of the objects can be updated either automatically or manually:
- The
ControlUpdateMode
property of the binding determines when the host object is automatically updated. The value ControlUpdateMode.Never
indicates the host object is not to be updated automatically. The value ControlUpdateMode.OnPropertyChanged
(the default) indicates the host object is updated whenever the data source object publishes a property changed event for the bound property. - The
DataSourceUpdateMode
property of the binding determines when the data source object is automatically updated. The value DataSourceUpdateMode.Never
indicates the data source object is not to be updated automatically. The value DataSourceUpdateMode.OnValidation
(the default) specifies the data source object is to be updated whenever the host object publishes a Validated
event. The value DataSourceUpdateMode.OnPropertyChanged
specifies the data source object is to be updated whenever the host object publishes a property changed event for the bound property. - The
ReadValue
method of the binding updates the host object from the data source object. - The
WriteValue
method of the binding updates the data source object from the host object.
Binding objects can interpret two types of property changed events: the PropertyChanged
event defined by the System.ComponentModel.INotifyPropertyChanged
interface, or a property-specific propertynameChanged
event like those implemented by Control
objects. The PropertyChanged
event is preferred.
Participating in Data Binding
There are two roles available in a data binding: the data source and the binding host. This section discusses the code needed to support these two roles.
Participating As A Data Source
Objects may become data sources simply by publishing property changed events. The System.ComponentModel.INotifyPropertyChanged
interface is the preferred mechanism for publishing property changed events. This interface defines the PropertyChanged
event. Subscribing to the PropertyChanged
event requires a PropertyChangedEventHandler
delegate, which in turn uses the PropertyChangedEventArgs
event arguments. The PropertyChangedEventArgs
class defines a string
-valued PropertyName
property, which contains the name of the property for which the value has changed. The PropertyName
property can be null
or string.Empty
to indicate all properties of the publishing object have changed.
Property changed events must be published only when a property value actually changes, not simply when the property undergoes assignment. A typical non-bindable property might be implemented as follows:
public string Value { get; set; }
This instructs on the compiler to generate backing store and simple get
and set
accessors.
An “industrial strength” bindable version of the same property might be as follows:
public static readonly string ValuePropertyName = "Value";
protected string _value;
public string Value
{
get
{
EnsureNotDisposed ();
return _value;
}
set
{
EnsureNotDisposed ();
bool change = !string.Equals (_value, value);
if (change)
{
_value = value;
PublishPropertyChangedEvent (ValuePropertyName);
}
}
}
This version of the property addresses a number of issues:
- When creating bindings, property names are specified as text strings. Mistakes are caught only at run time, when the binding throws an exception. The
ValuePropertyName
member provides a string
for the property name which can be used in creating the binding. This guards against misspellings at either the binding site or at the property change publication site. - The calls to
EnsureNotDisposed()
are part of a disposition pattern. Typically business objects have the IDisposable
interface in their heritage, and a base class that provides hooks for disposition, finalization, and so forth, including a check to determine whether the object has already been disposed. The check to determine if the object is already disposed is here assumed to be EnsureNotDisposed()
. - The flag
change
indicates whether the assignment to the property is a change in the property’s value. Here the value assigned is compared against the backing store, _value
. As this property does not reject null
values, the static string.Equals
method is used to allow either _string
or the value
assigned value to be null
. The pattern of a separate computation of a change
flag permits easy introduction of more complex change determination. For example, the business object might want to consider null
and string.Empty
to be equivalent. - Finally, if the assignment to the property results in a change to the property’s value, the backing store is updated and the property changed event is published. Here, the publication is delegated to a method called
PublishPropertyChangedEvent
accepting the name of the property for which the value changed.
Best practice in event publication requires several non-obvious steps. A null
delegate must not be invoked, so the EventHandler
subscriber list must be copied before being tested for <null>null
: this avoids a race condition that can occur should the last subscriber to an event unsubscribe between the test for null
and the delegate invocation. The EventChangedEventArgs
object need be constructed only for a non-null
subscriber list. The subscriber list must be expanded to its individual delegates, and each delegate called individually with exceptions caught and discarded: this prevents faulty event handlers from prematurely terminating the invocation of event handlers. Delegating the actual publication of the property change event to a PublishPropertyChangedEvent
method allows these best practices to be followed without cluttering up the individual properties.
Participating As A Binding Host
Objects may become binding hosts by implementing the System.Windows.Forms.IBindableComponent
interface. This interface defines two properties, BindingContext
and DataBindings
. The implementing class need only initialize these properties; thereafter, no interaction with them is necessary. Inheriting these properties from a base class is a highly attractive possibility.
The BindingContext
property has type BindingContext
. A BindingContext
object is a collection of BindingManagerBase
objects. BindingManagerBase
objects are CurrencyManager
objects or PropertyManager
objects. Each CurrencyManager
object manages a separate list of objects. This has important implications when several controls are to display different properties of the same element of a list. For the simple data bindings considered in this article, however, only PropertyManager
objects are involved. As PropertyManager
objects are interchangeable, the BindingContext
property of each binding host can be a newly allocated BindingContext
object. Sharing a single BindingContext
collection among a set of binding host objects affects only storage consumption. The code accompanying this article permits such sharing by implementing the BindingContext
property as read/write.
The DataBindings
property has type ControlBindingsCollection
. It must be initialized to a newly created ControlBindingsCollection
object. The ControlBindingsCollection
constructor requires a reference to the binding host object.
Data bindings do not have any particular special access to or knowledge of their hosting object. Therefore, in order for the bindings to become aware of changes to the hosting object, the hosting object must publish property changed events. The binding subscribes to property changed events for its host object just as it subscribes to them for its data source object. The hosting object should therefore implement the INotifyPropertyChanged
interface as well as the IBindableComponent
interface, and implement PropertyChanged
notifications for all interesting properties so that they can be data sources.
As the name implies, the IBindableComponent
interface inherits from the System.ComponentModel.IComponent
interface. Classes which host data bindings must therefore implement the IComponent
interface. The IComponent
interface defines the Disposed
event and the Site
property. The IComponent
interface inherits from the IDisposable
interface, which defines the Dispose()
method. A standard disposition framework can be inherited from the System.ComponentModel.Component
class.
Participation Summary
In many applications, the base class of the application class hierarchy implements IComponent
and INotifyPropertyChanged
. Implementing these lets objects be manipulated in the Visual Studio user interface designer and participate in data binding as data sources for the user interface. In such applications, objects already support participation in data binding as data sources. Adding support for participation in data binding as binding hosts requires only implementing the IBindableComponent
interface, which adds two simple properties to the object.
Implementing Data Binding
The interfaces required for data binding, as either a data source or a binding host, can be almost completely implemented in a base class. The BindableComponent abstract
class in the example code attached to this article requires descendant classes only to note when property changes occur; all other support is provided by the base class.
The implementing code for IComponent
, IDisposable
, and INotifyPropertyChanged
is all standard and not affected by the data binding framework. The object should participate in the application’s standard disposition framework and standard event publication framework. A typical implementation of these frameworks is included in the ApplicationBaseClass abstract
class in the example code attached to this article.
Given an object implementing INotifyPropertyChanged
, implementing IBindableComponent
is straightforward: it is two simple properties. The implementations of these properties can be relegated to a utility class and inherited by any object needing this support. A typical implementation of this interface is included in the BindableComponent
class in the example code attached to this article.
There are two minor complications to implementing IBindableComponent
. The first is whether instances of the BindingContext
are to be shared. The binding context is a collection of binding manager objects. When used in a user interface, Control
objects typically discover their containing control and use its BindingContext
. The BindableComponent
is not a Control
, and does not need a container. The BindableComponent
class will create a BindingContext
for itself if needed, or other code can assign to its BindingContext
property. The example program included with this article assigns the main form’s BindingContext
to the example component’s hosting data bindings.
The second minor complication lies in the default update mode for data bindings. The DefaultDataSourceUpdateMode
property of the ControlBindingsCollection
object provides the default DataSourceUpdateMode
for bindings added to the collection; and that property determines the conditions under which changes to the hosting object will result in changes to the data source object. The default value is DataSourceUpdateMode.OnValidation
, which indicates the update will occur in response to validation. Inheriting classes which are not Control
objects may want to set the DefaultDataSourceUpdateMode
property to DataSourceUpdateMode.OnPropertyChanged
instead. The example program included with this article does this.
Example Program
The example program shows data binding occurring in a chain of four objects. The two end objects are System.Windows.Forms.TextBox
controls. The two interior objects in the chain are IntermediateObject
objects; IntermediateObject
is an example BindableComponent
implementation. The IntermediateObject
objects also display their values on additional (read-only) TextBox
controls to reveal their state.
Two IntermediateObject
objects are added to the main form as components named IntermediateObject1
and IntermediateObject2
. The data bindings among the objects are set up in the OnLoad
override in the main form. The key lines are:
TextboxA.DataBindings.Add ("Text", IntermediateObject1, "Value");
IntermediateObject1.BindingContext = BindingContext;
IntermediateObject1.DataBindings.Add ("Value", IntermediateObject2, "Value");
TextboxB.DataBindings.Add ("Text", IntermediateObject2, "Value");
This sets up three data bindings to bridge the gaps between the four components. The main form’s binding context is used for the IntermediateObject
object’s binding context.
Running the application presents a simple form with four TextBox
controls. You can enter data into the two TextBox
controls labelled “Textbox A” and “Textbox B.” These are the controls referred to in the code. The TextBox
controls labelled “Intermediate 1” and “Intermediate 2” show a view into the intermediate objects IntermediateObject1
and IntermediateObject2
. These TextBox
controls are not updated by data binding, but by explicit action of the IntermediateObject1
and IntermediateObject2
components.
Entering data into the TextBox
labelled Textbox A, and pressing TAB to leave the control, results in a screen like that shown in the screen shot.
Note how the value entered into Textbox A was copied to IntermediateObject1
through the binding hosted by TextboxA
, then to IntermediateObject2
through the binding hosted by IntermediateObject1
, and finally to TextboxB
through the binding hosted by TextboxB
.
Entering data into the TextBox
labelled Textbox B, and pressing TAB to leave the control, will similarly cause data to be copied. The value entered into Textbox B would be copied to IntermediateObject2
by the binding hosted by TextboxB
, then to IntermediateTextbox1
by the binding it hosted, and finally to TextboxA
by the binding it hosted.
Summary
Simple data binding is a mechanism by which properties of one object can be automatically copied to properties of another object. This is quite useful when the two objects are two views of the same underlying data. The ability serves as a data source for data binding involves implementing the INotifyPropertyChanged
interface. The ability to serve as a host for data binding involves additionally implementing the IBindableComponent
interface. The code included with this article shows an example of such an implementation. The implementation is packaged in an abstract
base class that allows any deriving class to host data bindings.
Acknowledgements
The author gratefully acknowledges Bruce Carlson of A123 Systems, Inc., who supported the development of both the software described in this article and this article itself.
History
- 1 October 2010: Original version of article