Binding Business Objects to WinForms

Contents

Introduction

I guess most readers agree that a well-designed application consists of some logical layers or tiers. Common sense dictatess to at least separate the user interface from the underlying business objects and rules and the database access layer. Usually, business objects (also referred to as business entities; I use these terms here equally) are available in all layers and are the data source for Windows forms. Binding these business objects to forms can be a boring and error-prone task. With a new WinForm application coming up, I decided to finally tackle this inconvenience. I found myself writing code like this just too many times to tolerate this monotony and possible error source any longer:


private void initialize()
{
txtName.Text = _person.Name;
dateOfBirth.Value = _person.DateOfBirth;
chkActive.Checked = _person.Active;
}
private void save()
{
_person.Name = txtName.Text;
_person.DateOfBirth = dateOfBirth.Value;
_person.Active = chkActive.Checked;
}

This might be a non-issue when there are only a few fields, everything is specified and will not change in the course of a project, and the project contains only a few forms. Facing, however, maybe up to hundred or more forms, I wanted to bind these objects in the designer, assigning each input control a property of the business entity with just a few clicks. A few months ago, I covered a way to bind XML documents—config files in particular—to WinForms, so I thought it should be an easy task to extend support of binding to any object.

Design Goals

The proposed solution is supposed to:


  1. Simplify the binding of one business entity to a WinForm, in which this entity is created/edited.
  2. Provide a consistent method of handling the IsDirty state of the object (for example, show an asterisk in the caption when the object is dirty and remove it when it is not dirty).
  3. Show up a consistent method of indicating the IsValid state of the object (for example, using the ErrorProvider component).

How to Read the Code

To help you understand the code (especially the scope of methods and variables), here are the relevant coding conventions I follow:


  • Private fields start with a “_” prefix followed by lowercase.
  • Private methods start with lowercase.
  • Public or protected methods and properties (there are no non-private fields) start with uppercase.

For clarity’s sake, I omitted helper functions in this article where I thought that the implementation is rather trivial and the method name tells the story anyway (which a method name should always do). You can look them up in the source code, though.

The ObjectBindingManager

The ObjectBindingManager is the key component of this package. It abstracts the bound object and provides design-time capabilities for assigning properties to controls. The idea behind it is rather trivial. The component gets dropped on the WinForm. It is then assigned a System.Type to allow the assignment via a PropertyExplorer instead of typing a property name and thus avoid errors. At runtime, the ObjectBindingManager initializes all bound controls with the corresponding properties. It further provides a CommitChanges() and a RollbackChanges method to handle the updated values. It also listens to the Validated event of all bound controls, compares the new value with the old one, and raises an IsDirtyChanged event to notify a listener about the state of the object. By this, the form can listen to this event and behave accordingly (for example, the mentioned asterisk; but it could also activate/deactivate the Save button).

The BoundType is assigned by means of a custom UITypeEditor called TypeBrowser. I incidentally found this component when I just wanted to write my own, so kudos here to Stephen Toub. I modified it slightly to fit my needs. It now takes a System.Type as parameter and shows only types of this Type or descendants. By this, you can modify my code and provide the base type of your business objects. If you don’t have a base type, simply call it with a NULL argument. It then shows all types. The ObjectBindingManager provides extended properties, a very convenient technique to enhance other controls. All you have to do is to tag the component with a ProvideProperty attribute and follow a simple naming convention:


private bool ShouldSerializeBinding(Control extendee)
{
Binding binding = _bindings[extendee];
if (binding == null) return false;

bool serialize = (binding.ControlProperty != null
&& binding.ControlProperty != “” &&
(binding.ObjectProperty != null &&
binding.ObjectProperty != “”) ||
binding.MonitoredOnly);

return serialize;
}

[Category(“Data”)]
[Description(“Define databinding on Object”)]
public Binding GetBinding(Control extendee)
{
if (_bindings.ContainsKey(extendee))
return _bindings[extendee];
else
{
Binding binding = new Binding(ObjectType, extendee);
binding.ControlProperty = (extendee is CheckBox) ?
“Checked” : “Text”;
_bindings[extendee] = binding;
return binding;
}
}

public void SetBinding(Control extendee, object value)
{
Binding binding = value as Binding;
binding.Control = extendee;

if (!binding.MonitoredOnly &&
(binding.ControlProperty == null ||
binding.ObjectProperty == null))
_bindings.Remove(extendee);
else
if (_bindings.ContainsKey(extendee))
_bindings[extendee] = binding;
else
_bindings.Add(extendee, binding);
}

Here, the property binding will be provided to all controls. You can select a control in the designer, switch to the property window, and you find an entry “Binding on bindingManager1” under the DataBinding category. The ShouldSerializeBinding method here tells the designer whether code for this property should be generated.

Supporting Complex Types

Depending on the controls you use and your business framework, you might still need to assign some values manually. In the sample application, you will find a Flags property that is displayed by two checkboxes. In case you can’t use the auto-binding feature but still want to track the IsDirty-state, you can “monitor” a control. Select the control, find the “Binding on bindingManager1” entry, expand it, and set MonitoredOnly to true. Now, the initial state of the control is compared against the actual state when determining the IsDirty state. After manual initialization, call bindingManager.ResetMonitoredValues();.


public MainForm()
{
InitializeComponent();

Person person = Service.GetPerson();
bindingManager.BoundObject = person;
bindingManager.Bind();

chkUser.Checked = (person.Flags & GroupFlags.User)
== GroupFlags.User ? true : false;
chkAdmin.Checked = (person.Flags & GroupFlags.Admin)
== GroupFlags.Admin ? true : false;

bindingManager.ResetMonitoredValues();
}

How to Use the Code

Compile the code, add the ObjectBindingManager to your toolbox, and drop an instance on the form. Assign a BoundType, select a control to bind, find ‘Binding on bindingManager’ in the Data category of the property window, and assign the ObjectProperty.

Now, Where Do I Go from Here?

Because business object layers and needs differ greatly from each other, this is most likely not a Plug&Play component. I assume you will modify the argument to TypeBrowser, so only your business objects are displayed. Furthermore, you might enjoy the simplicity of handling the IsDirty state and think of enhancing the BindingManager to also provide the IsValid state—well, at least I did…. If you have a strong and descriptive validation on your business objects (like, for example, DataObjects.NET or CSLA.NET does), you might greatly reduce the need of user interface validation code. The component has an ErrorProvider property—just drag one on the form, switch to the properties of the ObjectBindingManager, and assign it—which I use to display the validation errors.

You can switch the ObjectBindingManager to a different mode by disabling CacheValues. Instead of caching the values in an object array, the properties are directly bound using the DataBindings collection of the controls. I decided to enable it by default simply because I needed to. It might suit your needs better by disabling it.

Finally, I can only recommend using a kind of EditorBaseForm where you put the ObjectBindingManager, and handle its events and provide a template Save method. It saved me a great deal of time and headaches!

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read