By now you have been playing around with the .NET Framework and in many
cases actually building applications. You have been introduced to ASP.NET,
Web services, and Windows Forms, and you've seen ADO.NET in action. ASP.NET
has certainly shown you that utilizing data binding is not only an option
but in many cases the absolute best way to fill controls with information.
The same can be said for Windows Forms applications where you have seen how
to bind controls to ADO.NET objects such as the DataView and the DataTable.
You may have even have seen how you can bind controls to simple arrays and
lists of information. In this article I'll take it up a notch and show you
not only how you can bind controls to ADO.NET objects and arrays, but how
the power of the .NET Framework also allows you to bind your classes to
controls.
For many years, data binding was shunned by developers and architects
for a number of reasons. For one, data binding was thought to cause
scalability problems because the binding required live database connections.
Until disconnected data was able to be bound in ADO, live database
connections limited the usefulness of data binding. Second, because one of
their goals is to shield the user interface from the structure of the
database, architects avoided the use of data binding since it caused the
user interface to be tightly bound to the database structure. Because of
these past issues, many developers and architects still shun data binding
(although it is extremely difficult to take advantage of ASP.NET features
without using data binding). This article shows that these issues are no
longer valid and, in fact, shows how ignoring data binding will result in
wasted time when building your applications because of the development time
that data binding can save.
This article concentrates on binding classes to Windows Forms controls
because that is where most of the innovation with data binding has occurred.
ASP.NET does not provide this functionality. I will discuss how to data-bind
your classes to Windows Forms controls and I will show you how to build a
collection of classes that can be data-bound to Windows Forms controls,
including DataGrids. I will also show you some interesting ways that you can
manage the controls that visually represent your class's properties.
Simple Binding of Classes to Controls
The most tedious part of building a rich-client application with VB
using an object-oriented approach was the glue code that you needed to write
to move data from controls that represented properties to the class
properties and vice versa. You had to handle control-change or lost-focus
events; you often had to handle exceptions thrown by the object property
procedures (Errors in pre-VB.NET days) because of data validation. What if
some code in a form changed the value of a property? What if some code in a
class changed the value of a property?
These few items are just a couple of the issues you don't need to worry
about when you data-bind your class to your control. Listing 1 shows an
Employee class and how to data-bind a property of the class to a TextBox
(all of the code for this article is available for download from
below). When you execute the sample code, things appear to work well, until you change the class property value with code rather than changing the
value of the TextBox. What happens or actually, what doesn't happen is
that the TextBox text will not change to the new value in the class
property. To accomplish this you need to add events for the properties that
can change. Listing 2 shows the same class with the events required to cause
changes to class properties from code rather than user interaction to be
reflected back to the controls bound to the properties.
Now let's take a step back and look at the objects that are involved
with the code in Listings 1 and 2 from both a direct and an indirect
perspective. First let's look at the System.Windows.Forms.Binding (Binding)
type. An instance of this type is created by the Control.Databindings.Add method or it can be created in code and then added to the Control.Databindings Collection. The key properties of the Binding type are summarized in Table 1.
In addition to the properties mentioned previously, there are two other
members of the Binding type that are of interest: Format and Parse events.
The Format and Parse events allow you to translate information contained in
the class into a format the control can handle and then again format the
information back into the class property. Listing 3 uses the Format event to
add currency symbols to the value of the Salary property and to remove the
currency symbols when the value in the control is changed.
The information within a class property may not be of the proper type
for the property of the control to which you are binding it. The Format and
Parse events of the Binding object allow you to control the conversion when
data is moving from the class property to the control property and vice
versa (see Listing 3).
Digging deeper, we discover that every form automatically has an
instance of the System.Windows.Forms.BindingContext (BindingContext) type, which is available through the Form.BindingContext property. The BindingContext is a collection of System.Windows.Forms.BindingManagerBase (BindingManagerBase) types that contains an item
for every data source (e.g., ADO.NET DataTable, ArrayList, Custom Object)
used in data binding on the form. In the next section I will show you how to
use the BindingManageBase type to provide for navigation of collections of objects that are data-bound to controls.
Complex Data Binding of Collections to Controls
When I design user interfaces I generally use 90% simple data binding,
but in some cases I need to bind a collection of objects to controls rather
than just one instance of a class. The .NET Framework supports this option
as well. To do this we need to understand the use of interfaces defined in
the .NET Framework.
There are a number of interfaces involved with data binding, the most
important being the System.ComponentModel.IBindingList. If you create an
object that you want to be bindable to a grid or to support data-binding a
list to controls such as TextBoxes or CheckBoxes, you will need to implement
this interface. Listing 4 shows an abstract class called BindableCollection
and an Employees class that inherits from the BindableCollection abstract
class. The IBindingList interface is large, but in most cases the
implementation is simple (and can be abstracted away so you inherit from a
class that does the implementation, as is the case with my example).
The key to the implementation is the raising of events when an item in
the collections is modified in any way. This is accomplished via the
OnClear, OnSet, OnInsert, and OnRemove event handlers for the collection and
also via a method, CollectionItemChanged, that is callable by items that are
added to the collection.
To operate correctly, the items in the collection need to call the
collection's CollectionItemChanged method whenever the values change. The
CollectionItemChanged event will notify controls that are data-bound to the
collection. Now you can data-bind a collection to a DataGrid or simple
controls, as shown in Listing 5. The code in Listing 5 is fairly simple and
illustrates how the key to binding is the construction of the class. To the
developer using the class in the user interface, it is just another type.
Creating the BindableCollection abstract class simplifies the creation
of collections that implement the IBindingList interface. Listing 4 shows my
sample BindableCollection abstract class and the Employees class that inherits from it.
Binding to a Collection
Binding data to a grid requires the class to implement IBindingList, but
the code to data-bind the class to the grid or simple controls is simple
(see Listing 5).
Navigating Through Complex Data-Bound Classes
Now that we have created a complex data-bound class and have data-bound
an instance to a control, you may be wondering how to navigate through the
individual items. This brings us back to the BindingManagerBase type. The
BindingManagerBase type has a number of methods to facilitate navigation
through classes that implement IBindingList. First, the BindingManagerBase
is an abstract type and two types in the .NET Framework inherit from it. The
two types are the System.Windows.Forms.CurrencyManager (CurrencyManager) type and the
System.Windows.Forms.PropertyManager (PropertyManager) type.
The runtime will create one of these two types based on what you
data-bind to. The PropertyManager type is created when you data-bind to a
simple type (such as a single object) and the CurrencyManager is used when
you data-bind to a complex type (a type that implements IBindingList). When
our Employees collection class in Listing 4 is bound to controls, a
CurrencyManager type is created and added to the BindingContext collection.
The CurrencyManager type has the methods described in Table 2, which are
used for navigation.
Navigation becomes an exercise in manipulating the CurrencyManager. Listing 6 manipulates the CurrencyManager with the common navigation that developers provide for user interfaces. When changing the value of a property with standard types, the standard object.property syntax is used.
You've learned how to data-bind simple and complex classes to a variety
of controls. Now let's take a step back and look at what properties of
controls we can data-bind and then let's take a second look at the
capabilities of the Binding type.
Controlling the User Interface Through Data Binding
Typically, developers look at data binding as a way to fill controls
with values that users manipulate. This is not the only possible use. For
instance, a data-entry form may take in information for an employee in my
example. Typically, a class like an employee will have an IsValid flag or an
IsDirty flag. With data binding we can control the ability to click the Save
button on a form by binding the Enabled property of the button to the
IsValid property of the employee. So now, as properties of the employee are
manipulated, the IsValid property of the employee will be set to the proper
setting and as soon as the employee is valid, the Save button will enable.
This is in contrast to how developers use timers to enable the Save button
by periodically checking the IsValid property and enabling or disabling the
button in the Timer event. This is just one example of when you might
data-bind to a control for which you typically wouldn't use data binding.
Data binding is extremely powerful when you extend your thinking to
properties and controls that you hadn't thought about before. In some of the
work my company is doing, we utilize data binding to bind to many properties
of a control. For instance, we use data binding on TextBoxes to control the
properties in Table 3.
The key to this is adding additional information to each of your
properties in a business class. After all, you want to indicate the
information for each business class property. For instance, for the employee
class we want to have this additional information for all of the properties.
Then in the implementation of the class, we will manipulate the additional
information based on a number of criteria such as validation, security, and
object state. So the key question now is how to add the additional details
to each of the properties. I have explored two methods of doing this. The
first is the method I used with VB6 and its style of data binding, which
involved an internal collection of Attribute objects that contained one item
for each property. The other method was to explore inheritance and
containment to create richer types to be used as the types exposed by the
properties.
So the goal was to create a richer string type, integer type, datetime
type, etc. The problem was that the string, integer, and datetime types are
all sealed and cannot be inherited from. So that meant we had to resort to
containment to extend the types. The only problem with this is that now the
value of the FirstName property, for instance, of the employee type had to
be manipulated using code similar to that in Listing 6 rather than the code
in Listing 7. We created a base type called BindingType and then inherited
from that to create BindingString, BindingInteger, BindingBoolean, etc. These new types have the additional properties shown in Table 4.
This leads us into an exploration of the third parameter in the
constructor for a Binding type. The DataMember parameter is quite often
thought to be just the name of the property that you are data-binding, but
it actually consists of a path and a property. So for our employee class, a
possible DataMember value would be "FirstName.Text" or "FirstName.BackColor". In these examples, "FirstName" is the path and "Text" or "BackColor" is the property.
Conclusion
In the samples available for download, I have included a few different
ways that you can utilize data binding in an application. Applications that
are built in this manner are easily data driven and adaptable to user change
without code changes and redeployments. Take what you have learned in this
article and explore the .NET Framework. You will discover that the
data-binding capabilities are incredible and extendible. Explore how you can
create components that can be bound at design time, and explore different
methods of using data to manipulate your applications.
About The Author
Keith Franklin is one of the founders and leading forces behind Chicago's
premier .NET development services company, Empowered Software Solutions. In
addition to acting as chief software architect for Empowered, Keith directs
the Chicago.NET Users Group (CNUG) and is a founding board member of the
International .NET Association (INETA) and .NET Developer's Journal. He has
authored many articles on .NET technologies and has written the book VB.NET
for Developers from Sams Publishing.
ka_franklin@empowered.com
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail:
info@sys-con.com