English context:<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
Windows Forms Data Binding and Objects
Microsoft worked hard to make data binding useful when creating both Windows Forms and Web Forms interfaces. For the first time since data binding was introduced to Microsoft Visual Basic? years ago, it is truly practical in a wide range of application scenarios.
One key advancement is that data binding supports not only the DataSet, but also objects, structures and collections of objects, or structures. The basic ability to bind an object or collection to a control on a Windows Form or Web Form requires no extra work on our part. It is an automatic feature of data binding.
Web Forms data binding is read-only. In other words, it pulls data from the data source (DataSet, object, or collection) and uses the data to populate controls that are then rendered to the client device. This is straightforward behavior and requires no extra work on our part as we create either objects or the UI.
Windows Forms data binding is read-write, and therefore more complex. In this case, data is pulled from the data source and displayed in our UI controls, but any changes to the values in the controls are automatically updated back into the data source as well. Much of this behavior is automatic and requires no extra work on our part, but there are a number of features available to us if we choose to write some extra code.
we can add to our business and collection classes to better support the features of Windows Forms data binding. These features include:
· Having the object or collection notify the UI that data has changed.
· Allowing the DataGrid to bind correctly to an empty collection.
· In-place editing of child objects in a DataGrid.
· Dynamic addition and removal of child objects in a DataGrid.
For simple objects, we can implement events to notify Windows Forms data binding when our property values have changed. By adding these events, we enable the UI to automatically refresh its display any time the data in our object is changed. We also need to understand how to notify the UI that a validation rule has been broken by newly entered data. Improper implementation of validation can make data binding behave in undesirable ways.
In addition, there are a number of optional features we can support in our collections. Collections are typically bound to list controls such as the DataGrid. By properly implementing strongly-typed collection objects, we enable the DataGrid to intelligently interact with our collection and its child objects. We can also implement the IBindingList interface so our collection can intelligently interact with the DataGrid in various ways.
Finally, there are some optional features we can support in objects that will be contained within a collection. We'll call these child objects. Child objects can implement the IEditableObject interface so the DataGrid can properly interact with the object during in-place editing. The child objects can also implement IDataErrorInfo so the DataGrid can mark the line as being in error if any validation rules are broken as the data of the object is changed.
In the end, by adding a bit of code to our classes and collections, we can take advantage of some very powerful features of Windows Forms data binding.
Simple Windows Forms Data Binding
Simply binding properties of an object to properties of controls on a form is not complex. For instance, consider the following simple Order class:
The only special code here is in the declaration of the variables:
Private mID As String = ""
Notice that the variables are initialized with empty values as they are declared. This is not typical code because Visual Basic .NET automatically initializes variables when they are declared.
The reason we explicitly initialize them like this is because if we don't, the data binding will fail. It turns out that the automatic initialization of the variables doesn't occur by the time data binding tries to interact with our object, causing a runtime exception to be thrown when data binding attempts to retrieve the value from the uninitialized variables.
However, explicit initialization of the variables occurs before data binding interacts with our object. This means that the variables are properly initialized by the time data binding retrieves their values so we avoid the runtime exception.
If we create a form similar to the one shown in Figure 1, we can simply bind the properties of the object to the controls as the form loads.
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
Figure 1. Simple Form layout for the Order object
The code to bind an Order object to the form would look like this:
The secret lies in the fact that every Windows Forms control has a DataBindings collection. This collection contains a list of bindings between the properties of the control and the properties of our data source (or data sources). An interesting side effect of this scheme is that we can bind properties from a data source to several different control properties. Also, we can bind different control properties to properties from multiple data sources.
Just by using this simple data binding code we can create some pretty complex user interfaces. For example, in the sample code for this article you'll find that we bind the Enabled property of the Save button to an IsValid property on the business object. This way the button is only available to the user when the object is ready to be saved.
Remember that this data binding is bi-directional. Not only is the data from the object displayed on the form, but any changes the user makes to the data is automatically updated back into our object. This occurs when the user tabs off each field. For instance, if the user changes the value in the txtID control, the new data is updated into the object as the user tabs out of the control. The data is updated into our object through the property Set routine. This is nice because it means our existing Property code is automatically invoked; we don't need to do anything extra to support bi-directional data binding.
Notification of Changed Properties
Now that we've seen how simple data binding an object and the controls is, let's discuss how we can enhance our object to support automatic notification of changed properties. The issue here is that if some other code in our application changes the data in an object, there is no way for the controls in the UI to know about that change. The result is that the UI and object get out of sync and the user won't see the correct data in their display.
What we need is a way for the object to notify the UI any time a property value changes. This is supported through events that we can optionally declare and raise from our object. When we bind a control to a property on our object, data binding automatically starts listening for a property changed event named propertyChanged, where property is the name of the property of the object .
For instance, our Order class defines an ID property. When the ID property is bound to a control, data binding starts listening for an IDChanged event. If this event is raised by our object, data binding automatically refreshes all controls bound to our object.
We can enhance our Order class by declaring these events:
Notice that the events are declared to be of type EventHandler. This is required for data binding to understand the event. If we don't declare the events this way, we'll get a runtime exception when data binding attempts to interact with our object.
The EventHandler is the standard event model used through Windows Forms. It defines the event with two parameters—sender (the object raising the event) and e (an EventArgs object).
With these events declared, we need to make sure to raise these events any time the corresponding property value changes. One obvious place to do this is in the Set routines. For instance, in the ID property we would do this:
What can be trickier is to remember that any time we change
mID anywhere in our class, we also need to raise this event. Most classes include code that modifies internal variables in addition to property Set routines. We must raise the appropriate event any time a value is changed that will impact a property.
For a better example, let's assume that our Order object will have a collection of LineItem objects. We'll implement the collection a bit later, but right now let's look at the event and variable declarations of the basic LineItem class:
Notice that we have four events, one for each of our properties, but we only have three variables. The Amount property will be calculated by multiplying Quantity and Price:
It is a read-only property. However, it can change. In fact, it will change any time that the value of either Price or Quantity changes, and thus we can raise an event to indicate that it has changed. For instance, when the Price changes:
Not only do we raise the PriceChanged event because the Price property changed, but we also raise the AmountChanged event because we've indirectly caused the Amount property to change as well. This illustrates how we must be vigilant in our coding to ensure that these events are raised when appropriate.
That said, it turns out that the AmountChanged event may not be strictly necessary. When we bind controls on a form to properties of an object, data binding listens for propertyChanged events for each property to which our controls are bound. If any one of them is raised, all the controls bound to that object are refreshed.
In other words, if our form has controls bound to both the Price and Amount properties, raising the PriceChanged event will cause data binding to refresh not only the control bound to the Price property, but also the one bound to the Amount property.
The downside to taking advantage of this fact is that the UI becomes tightly bound to the object implementation. If we later decide to only bind to Amount, our UI won't work correctly because no AmountChanged event will be raised. Because of this, it is best to declare and raise a propertyChanged event for each property on our object.