Click here to Skip to main content
15,879,095 members
Articles / Programming Languages / C#
Article

Looking for the missing link - Binding business objects to WinForms

Rate me:
Please Sign up or sign in to vote.
4.77/5 (29 votes)
27 Sep 20046 min read 97.3K   1.1K   84   20
Simplifying the creation of a user interface for business objects while providing a consistent mechanism for handling IsDirty and IsValid in the UI.

Sample Image - bindingmanager.gif

Contents

Introduction

I guess most readers agree that a well designed application consists of some logical layers or tiers. Common sense is 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:

C#
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 (e.g., 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 (e.g., 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 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 (e.g., 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 props 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:

C#
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 if 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 which 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
C#
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 provide also 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 reduce greatly 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!

Revision Notes

  • 27/09/2004: Fixed an issue concerning readonly properties and non-string properties.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here



Comments and Discussions

 
Questionformat end parse event Pin
manzyinter26-Dec-06 21:35
manzyinter26-Dec-06 21:35 
How can i use format and parse events of a System.Windows.Forms.Binding object with ObjectBindingManager library?

thanks
Massimo
QuestionIsValid Pin
Spink Bickle31-Jul-06 4:42
Spink Bickle31-Jul-06 4:42 
AnswerRe: IsValid [modified] Pin
Eduard Liebenberger31-Jul-06 19:15
Eduard Liebenberger31-Jul-06 19:15 
GeneralOut of memory Pin
Steven Campbell6-Jan-05 5:53
Steven Campbell6-Jan-05 5:53 
GeneralBinding to Two or More Combo Boxes Pin
leonleslie27-Nov-04 17:09
leonleslie27-Nov-04 17:09 
GeneralRe: Binding to Two or More Combo Boxes Pin
Eduard Liebenberger14-Dec-04 23:45
Eduard Liebenberger14-Dec-04 23:45 
GeneralReflectionTypeException Error on Object Binder Manager -> ObjectType Pin
Anonymous18-Nov-04 14:43
Anonymous18-Nov-04 14:43 
GeneralRe: ReflectionTypeException Error on Object Binder Manager -> ObjectType Pin
Eduard Liebenberger19-Nov-04 0:51
Eduard Liebenberger19-Nov-04 0:51 
GeneralRe: ReflectionTypeException Error on Object Binder Manager -> ObjectType Pin
Anonymous24-Nov-04 14:57
Anonymous24-Nov-04 14:57 
GeneralRe: ReflectionTypeException Error on Object Binder Manager -> ObjectType Pin
Eduard Liebenberger24-Nov-04 20:05
Eduard Liebenberger24-Nov-04 20:05 
GeneralMaybe a little bug Pin
EPO26-Sep-04 22:40
EPO26-Sep-04 22:40 
GeneralRe: Maybe a little bug Pin
Eduard Liebenberger27-Sep-04 0:13
Eduard Liebenberger27-Sep-04 0:13 
GeneralRe: Maybe a little bug Pin
EPO28-Sep-04 4:30
EPO28-Sep-04 4:30 
GeneralRe: Maybe a little bug Pin
Eduard Liebenberger28-Sep-04 4:35
Eduard Liebenberger28-Sep-04 4:35 
GeneralWonderful job ! Pin
PatrickVD13-Sep-04 11:24
PatrickVD13-Sep-04 11:24 
GeneralGood Job! Pin
alper13-Sep-04 10:58
alper13-Sep-04 10:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.