Click here to Skip to main content
15,880,796 members
Articles / Desktop Programming / Windows Forms
Article

Understanding Simple Data Binding

Rate me:
Please Sign up or sign in to vote.
4.83/5 (40 votes)
4 Sep 20058 min read 240.9K   2.1K   105   24
Tricks, pitfalls, and work arounds to .NET's data binding.

Introduction

Property binding is one of the slick things that .NET supports. It allows you to easily bind a property of a control to a property of a container class, which in turn helps to separate the UI implementation from the business logic. However, to properly utilize data binding you need to be aware of what .NET expects in your container and you need to be aware of the limitations of .NET data binding. In the end, you may throw out .NET's implementation completely!

This article will discuss those issues with regards to simple property binding. By simple, I mean a 1::1 relationship between the container property and the control property. This article will not discuss more complicated binding involving the CurrencyManager, data sets, etc. Simple property data binding turns out to be complicated enough!

First, a simple example

Image 1

As the above screenshot illustrates, we're going to create a form with a first and last name TextBox control, and we're going to expect that the full name TextBox control will be properly updated when we edit the text. There are a couple of buttons that perform the following functions:

  • updating the TextBox's Text property directly,
  • updating the container's property.

First, let's look at the code that sets this up (no, there's no MyXaml here, folks!).

The form

The form is defined straightforward enough, including the event handler that updates the control's text. I've made a few controls accessible outside of the form, as we'll use them later on.

C#
public class MyForm : Form
{
  protected TextBox tbFirstName;
  protected TextBox tbLastName;
  protected TextBox tbCompositeName;
  protected Button btnChangeContainer;
  protected Button btnChangeContainer2;

  public TextBox FirstName
  {
    get {return tbFirstName;}
  }

  public TextBox LastName
  {
    get {return tbLastName;}
  }

  public TextBox CompositeName
  {
    get {return tbCompositeName;}
  }

  public Button ChangeContainer
  {
    get {return btnChangeContainer;}
  }

  public Button ChangeContainer2
  {
    get {return btnChangeContainer2;}
  }

  public MyForm()
  {
    ** snip -- all sorts of boring initialization stuff **
    
    btnChangeControls.Click+=
           new EventHandler(OnChangeControls);

    Controls.Add(lbl1);
    Controls.Add(lbl2);
    Controls.Add(tbFirstName);
    Controls.Add(tbLastName);
    Controls.Add(tbCompositeName);
    Controls.Add(btnChangeControls);
    Controls.Add(btnChangeContainer);
    Controls.Add(btnChangeContainer2);
}

  private void OnChangeControls(object sender, EventArgs e)
  {
    tbFirstName.Text="Marc";
    tbLastName.Text="Clifton";
  }
}

The container

The container contains fields for first, last, and full name and property getter/setters for each, plus it implements a ChangeContainer method that sets the first and last name to my girlfriend, "Karen" and "Linder":

C#
public class MyContainer
{
  protected string firstName;
  protected string lastName;
  protected string fullName;

  public string FirstName
  {
    get {return firstName;}
    set
    {
      if (firstName != value)
      {
        firstName=value;
        FullName=firstName + " " + lastName;
      }
    }
  }

  public string LastName
  {
    get {return lastName;}
    set
    {
      if (lastName != value)
      {
        lastName=value;
        FullName=firstName + " " + lastName;
      }
    }
  }

  public string FullName
  {
    get {return fullName;}
    set
    {
      if (fullName != value)
      {
        fullName=value;
      }
    }
  }

  public void ChangeContainer(object sender, EventArgs e)
  {
    FirstName="Karen";
    LastName="Linder";
  }
}

Essentially, that's the usual implementation for getters and setters. Note that the FullName property is set if you change the FirstName or LastName. Ideally, this is one of those cases where you'd like a protected setter but a public getter. Alas, such a thing is not possible. And whether FullName should even have a setter rather than be computed on the fly depends on what you're doing. However, for the purpose of this article, we'll give FullName a setter and be done with it.

Initializing the application

Initialization is straightforward:

C#
public static void Main()
{
  MyForm form=new MyForm();
  MyContainer container=new MyContainer();
  container.FirstName="Joe";
  container.LastName="Smith";

  form.FirstName.DataBindings.Add("Text", 
                            container, "FirstName"); 
  form.LastName.DataBindings.Add("Text", 
                             container, "LastName"); 
  form.CompositeName.DataBindings.Add("Text", 
                             container, "FullName");

  form.ChangeContainer.Click+=
        new EventHandler(container.ChangeContainer); 

  Application.Run(form);
}

And we're all set to see what happens.

And the survey says...

On initialization, the form looks great:

Image 2

If we type in a new first name and hit the tab key, the full name updates correctly:

Image 3

Some problems

If you click on Change Controls, we notice the first problem - the full name didn't change.

Image 4

It gets worse. If I click on the first name textbox, then tab to the second one, I get a real mess:

Image 5

The control has updated the container for the first name but the last name control pulled the old value out of the container so now I have chaos.

And the final problem - if I update the container, the edit controls don't do anything:

Image 6

Solving one of these problems

The first thing to recognize is that the data binding is essentially one way - we can update the container when we change the control, but if we change the container, the control isn't being updated (except that, during initialization, the container value is read from). So, let's get it working so that we can update the control when we make changes to the container.

In order to do this, we have to give the .NET data binding mechanism a tie-in. .NET uses some name munging to see if an event has been declared that it can use to tell if the value has changed. Essentially, it is the property name appended with "Changed". So let's do that. In the container, I'm going to add the following:

C#
public event EventHandler FirstNameChanged;
public event EventHandler LastNameChanged;
public event EventHandler FullNameChanged;

And, following the .NET convention, I will add Onxxx protected methods that fire those events accordingly.

C#
protected void OnFirstNameChanged(EventArgs e)
{
  if (FirstNameChanged != null)
  {
    FirstNameChanged(this, e);
  }
}

protected void OnLastNameChanged(EventArgs e)
{
  if (LastNameChanged != null)
  {
    LastNameChanged(this, e);
  }
}

protected void OnFullNameChanged(EventArgs e)
{
  if (FullNameChanged != null)
  {
    FullNameChanged(this, e);
  }
}

The Onxxx method is going to be invoked in the property setter. I'm going to use conditional compilation here so that you can easily switch between the mono-directional and the bi-directional implementations:

C#
public string FirstName
{
  get {return firstName;}
  set
  {
    if (firstName != value)
    {
      firstName=value;
#if Bidirectional
      OnFirstNameChanged(EventArgs.Empty);
#endif
      FullName=firstName + " " + lastName;
    }
  }
}

public string LastName
{
  get {return lastName;}
  set
  {
    if (lastName != value)
    {
      lastName=value;
#if Bidirectional
      OnLastNameChanged(EventArgs.Empty);
#endif
      FullName=firstName + " " + lastName;
    }
  }
}

public string FullName
{
  get {return fullName;}
  set
  {
    if (fullName != value)
    {
      fullName=value;
#if Bidirectional
      OnFullNameChanged(EventArgs.Empty);
#endif
    }
  }
}

Now when I change the values in the container, lo-and-behold, the controls update!

Image 7

This really is an excellent example of why you should implement proper property setters!

However, I still have a problem. If I click on Change Controls (which, if you recall, changes the TextBox's Text property value), it is clear that the container is not being updated:

Image 8

Why is this? It's because the data binding mechanism doesn't fire until the control loses focus, for example by tabbing off of it:

Image 9

Now granted, it isn't necessarily "correct" for the control value to be updated programmatically. The correct way would be to work through the business or data object, in which case our bidirectional solution would work. This is a good case for why one uses an MVC pattern, for example. However, the point is, if you change the control programmatically, you would really expect that the container should update as well. Or, you would at least like to have the option for that to happen. Now, the UI probably doesn't (and shouldn't) have the container instance. After all, that's what data binding is for - a separation of concerns. So to accomplish the functionality we really desire, we need to throw out .NET's data binding.

Our better binding

Our better binding mechanism is going to expect xxxChanged events just like .NET, but it's not going to be triggered on losing focus. To begin with, we'll change how we do the binding during application initialization, using our own binder class:

C#
BindHelper.Bind(form.FirstName, "Text", container, 
                                              "FirstName");
BindHelper.Bind(form.LastName, "Text", container, 
                                              "LastName");
BindHelper.Bind(form.CompositeName, "Text", container, 
                                              "FullName");

And the implementation uses a bit of reflection to find the event handlers and wire them up to a couple of generic handlers:

C#
public class BindHelper
{
  protected object src;
  protected string srcProp;
  protected object dest;
  protected string destProp;

  protected PropertyInfo srcPropInfo;
  protected PropertyInfo destPropInfo;

  public BindHelper(object src, 
       string srcProp, object dest, string destProp)
  {
    this.src=src;
    this.srcProp=srcProp;
    this.dest=dest;
    this.destProp=destProp;
  
    Type t1=src.GetType();
    Type t2=dest.GetType();

    // Get the PropertyInfo now 
    // rather than in the event handler.
    srcPropInfo=t1.GetProperty(srcProp);
    destPropInfo=t2.GetProperty(destProp);
  }

  public static void Bind(object obj1, string prop1,
                            object obj2, string prop2)
  {
    string event1=prop1+"Changed";
    string event2=prop2+"Changed";

    Type t1=obj1.GetType();
    Type t2=obj2.GetType();

    EventInfo ei1=t1.GetEvent(event1);
    EventInfo ei2=t2.GetEvent(event2);

    BindHelper bh=new BindHelper(obj1, prop1, obj2, prop2);

    // The signature of the event 
    // delegate must be of the form 
    // (object, EventArgs).
    // Although events don't have to be 
    // of this signature, this is a good
    // reason to comply with the .NET guidelines.

    // Unfortunately, .NET 1.1 does not 
    // handle event signatures that are 
    // derived from EventArgs when programatically 
    // adding a generic handler.
    // This "bug" is corrected in .NET 2.0!

    ei1.AddEventHandler(obj1, 
         new EventHandler(bh.SourceChanged));
    ei2.AddEventHandler(obj2, 
         new EventHandler(bh.DestinationChanged));

    bh.DestinationChanged(bh, EventArgs.Empty);
  }

  private void SourceChanged(object sender, EventArgs e)
  {
    object val=srcPropInfo.GetValue(src, null);
    destPropInfo.SetValue(dest, val, null);
  }

  private void DestinationChanged(object sender, 
                                         EventArgs e)
  {
    object val=destPropInfo.GetValue(dest, null);
    srcPropInfo.SetValue(src, val, null);
  }
}

There's no exception handling or checking to see if the event handler exists. There certainly should be, but I want to keep the code pure. However, the comment illustrates an important problem. In .NET 1.1, if the event args is a class derived from EventArgs, reflection will throw an assertion when you attempt to wire up an event handler that specifies the EventArgs base class. It shouldn't, because this is a perfectly valid call, and in fact, in .NET 2.0, it is my understanding that this has been corrected.

Notice there's a small "trick" here - the method we're calling is static, but it instantiates a BindHelper class, and the events are wired up to that specific instance. Thus, the event handlers have the instance specific information they need to perform the "source = destination" or "destination = source" assignments.

So, let's see what happens. When I click on Change Controls, now the container is being updated!

Image 10

But now I've created an interesting side affect. The Text property of the control is updated every time I type a letter, so now my data binding fires while I'm editing the control!

Image 11

Even better binding

This may, occasionally, be a desired behavior (for example, typing in a filename immediately updates the full text path displayed below it), or it may not be desirable. If it's not desirable, we need to simulate what the .NET data binding does, by tracking the control focus through the Leave event. First, in the Bind static method, we check if the source is a Control. I'm using a conditional flag here so you can swap this code in and out as well:

C#
#if ControlBindingEmulation
if (obj1 is Control)
{
  ((Control)obj1).Leave+=new EventHandler(bh.OnLeave);
}
#endif

And the handler implementation is:

C#
private void OnLeave(object sender, EventArgs e)
{
  object val=srcPropInfo.GetValue(src, null);
  destPropInfo.SetValue(dest, val, null);
}

And lastly, the SourceChanged handler needs to check if the source is a Control, and if so, is it focused. If those conditions are true, then it does not update the destination:

C#
private void SourceChanged(object sender, EventArgs e)
{
#if ControlBindingEmulation
  if (src is Control)
  {
    if (((Control)src).Focused)
    {
      // As long as the control has focus, 
      // don't update the container.
      // However, if the control does not 
      // have focus but the control value
      // changes, then we DO want to update 
      // the container. This fixes
      // a bug in the .NET implementation 
      // of data binding, if something
      // programatically changes the control 
      // value that we're bound to.
      return;
    }
  }
#endif
  object val=srcPropInfo.GetValue(src, null);
  destPropInfo.SetValue(dest, val, null);
}

Container - Container binding

Did you notice in our better binder, that there isn't anything that is specific to binding controls? We can, in fact, bind between container classes now, assuming that the containers implement the appropriately named events in their property setters. To illustrate this, I have a third button that, when activated through another conditional compilation flag, will illustrate how a second container is bi-directionally bound to the first container, and how changing the first and last name in the second container not only updates the first container but also the TextBox controls that the first container is bound to. This is a very useful technique - instead of one container needing an instance of another container, you can use container-container binding, which significantly reduces the entanglement of your code (for a performance price, of course!).

The second container is instantiated and bound to the first container:

C#
container2=new MyContainer();
C#
BindHelper.Bind(container2, 
             "FirstName", container, "FirstName");
BindHelper.Bind(container2, 
             "LastName", container, "LastName");
BindHelper.Bind(container2, 
             "FullName", container, "FullName");

When the Change Container 2 button is clicked, the following event is fired:

C#
private static void OnChangeContainer2(object sender, 
                                               EventArgs e)
{
  container2.FirstName="Bob";
  container2.LastName="The Great";
}

As you can see, it has updated the user interface, and you can verify that the first container has been updated as well using the debugger:

Image 12

We now have truly superior data binding capabilities!

An exercise for the reader

If updating the container value updates the Text property of the control, and updating the Text property of the control updates the value of the container, why doesn't the application crash with a stack overflow?

Conclusion

Hopefully this article has illustrated some of the complexities regarding simple property data binding. If you use this code, please make sure to add error checking and exception handlers! You will probably also want to be able to specify whether you want real-time binding or "on lost focus" binding, rather than using the conditional compilation flags I've used for demo purposes.

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


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
GeneralMy vote of 1 Pin
ricardofmfreitas30-Sep-09 5:41
ricardofmfreitas30-Sep-09 5:41 
GeneralRe: My vote of 1 Pin
doctorgu10-Jul-11 16:20
doctorgu10-Jul-11 16:20 
Generalexcellent article Pin
Donsw16-Feb-09 15:02
Donsw16-Feb-09 15:02 
QuestionVB.NET Conversion? Pin
Hooded_Prince9-Sep-08 3:33
Hooded_Prince9-Sep-08 3:33 
GeneralThank you for this excellent article [modified] Pin
Siegfried Glaser28-Dec-06 6:47
Siegfried Glaser28-Dec-06 6:47 
QuestionDatabinding to pictureboxes Pin
standgale24-Sep-06 11:46
standgale24-Sep-06 11:46 
QuestionCan't download sample Pin
tlongman28-Jul-06 9:54
tlongman28-Jul-06 9:54 
GeneralNice Job Pin
Paul Conrad25-Feb-06 12:17
professionalPaul Conrad25-Feb-06 12:17 
Smile | :)
QuestionHow to implement a RemoveBinding function? Pin
frag061021-Feb-06 7:43
frag061021-Feb-06 7:43 
AnswerRe: How to implement a RemoveBinding function? Pin
standgale2-Oct-06 16:01
standgale2-Oct-06 16:01 
GeneralRe: How to implement a RemoveBinding function? Pin
h1gh127-Jan-07 9:45
h1gh127-Jan-07 9:45 
GeneralRe: How to implement a RemoveBinding function? Pin
gquinn29-Jan-07 4:59
gquinn29-Jan-07 4:59 
GeneralRe: How to implement a RemoveBinding function? Pin
h1gh131-Jan-07 1:49
h1gh131-Jan-07 1:49 
GeneralI really like this technique Pin
MarcelG18-Jan-06 9:13
MarcelG18-Jan-06 9:13 
GeneralGetter/Setter.. Pin
Rocky Moore2-Oct-05 19:44
Rocky Moore2-Oct-05 19:44 
QuestionExample of an MVC pattern in C#? Pin
tafkat26-Sep-05 8:59
tafkat26-Sep-05 8:59 
GeneralExercise For The Reader - Answer Pin
S. Senthil Kumar7-Sep-05 3:05
S. Senthil Kumar7-Sep-05 3:05 
GeneralRe: Exercise For The Reader - Answer Pin
Marc Clifton7-Sep-05 3:16
mvaMarc Clifton7-Sep-05 3:16 
GeneralLOL Pin
Member 5346086-Sep-05 23:40
Member 5346086-Sep-05 23:40 
GeneralEndCurrentEdit Pin
Eric Woodruff4-Sep-05 11:00
professionalEric Woodruff4-Sep-05 11:00 
GeneralRe: EndCurrentEdit Pin
Marc Clifton4-Sep-05 11:06
mvaMarc Clifton4-Sep-05 11:06 
GeneralRe: EndCurrentEdit Pin
Eric Woodruff4-Sep-05 18:52
professionalEric Woodruff4-Sep-05 18:52 
GeneralRe: EndCurrentEdit Pin
Marc Clifton5-Sep-05 2:42
mvaMarc Clifton5-Sep-05 2:42 
GeneralRe: EndCurrentEdit Pin
Tim McCurdy6-Sep-05 10:08
Tim McCurdy6-Sep-05 10:08 

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.