Click here to Skip to main content
15,879,096 members
Articles / Desktop Programming / Windows Forms

Hosting Simple Data Binding

Rate me:
Please Sign up or sign in to vote.
4.88/5 (6 votes)
1 Oct 2010CPOL12 min read 25.7K   777   10  
Shows how to participate in data binding both as a data source and as a binding host

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:

C#
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:

C#
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:

C#
/// <summary>
/// The name of the Value property.
/// </summary>

public static readonly string ValuePropertyName = "Value";

/// <summary>
/// Backing storage for the Value property.
/// </summary>

protected string _value;

/// <summary>
/// Gets or sets the string value of the object.
/// </summary>

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:

C#
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --