Wednesday, July 2, 2008

Surviving WinForms Databinding

I've rarely had the freedom in my career to implement a Windows application using MVC-style separation of concerns. Generally I am told "just get it working". Now, if I already knew how to tier out an application, this wouldn't be a problem. But since I don't, and it would take me a good deal of time to figure out a satisfactory way of doing it, I haven't been able to justify spending the time, on the company dime.


But recently, I've been fortunate enough to work on a new software product without a hard ship-date, and having other obligations on my plate. This has given me the freedom to not spend every minute producing functionality. So I've been experimenting with implementing MVC with Windows Forms.


There are essentially two ways to get this done. You can:

  1. Write a bunch of manual event code to trigger view changes from model changes, and vice versa.
  2. Use databinding and save yourself the explicit event code.

This works great if all your datasources are DataTables, or DataViews, or other such framework classes that are designed from the ground up to work with WinForms databinding. But should you have the misfortune of not dealing with any record-based data, you'll find you have a much tougher road to walk.


If you, like I, choose option 2, and you happen to be working on anything more complicated than a hello world application, and you are truly committed to doing MVC both correctly, and with as little unnecessary code as possible, then you will undoubtedly spend, like I have, a lot of time banging your head against a brick wall, trying to figure out why your databinding isn't doing what it is supposed to. This is a terrible shame, because if databinding in WinForms worked properly and was easy to use, it would be a spectacular tool for saving time and shrinking your codebase.


Truth is, you can still same time and code. But not as much as you might think when first introduced to the promise of databinding. If you can find any decent information on the obstacles you'll encounter. Compounding the above hurdles is the fact that what information can be found online about them is scattered to the four winds, and no one bit references the rest. So, I've decided to blog the headaches I encounter, and my resolutions, as I find them. This should increase the searchability of the issues at least a bit, by tying together the separate references with my blog as a link between them, and also by containing the different bits of information within one website. Or it would, if I had readers...


My results so far have produced 5 rules, to help you preserve your sanity while using WinForms databinding.


Rule 1: Use the Binding.Format and Binding.Parse events.


This first rule isn't actually too hard to find information on. Format and Parse essentially let you bind to a datasource property that doesn't have the same data type as the property on the control. So you can bind a Currency object to a TextBox.Text property, for example.


The Format event will let you convert from your datasource property type to your control property type, and Parse will let you convert from your control property type to your datasource property type. The MSDN examples at the links above are pretty good. If you use them as a template, you won't go wrong. But if you start to switch things up, beware Rule 2...


Rule 2: If you use Format or Parse events, DO NOT add the Binding to the control till after register with the events.


I honestly don't know what the deal is with this one. I just know that if you add your events to your Binding object after you've already passed it to the Control.DataBindings.Add function, they won't get called. I don't know why this should be, unless the control only gets a copy of your actual Binding object, not a reference to it.


Unfortunately, I have lost the references I had to the forum posts that talked about this. There were several, and now I can find none of them. I know I saw them, though, and I saw the symptoms of the other ordering, so as for me, I'm going to make sure to follow this rule.


Rule 3: Use INotifyPropertyChanged and/or INotifyPropertyChanging.


I ran across this info in a post on Rick Strahl's blog. I ran across this information during a desparate scramble to find out why my datasource ceased to be updated by user actions on the controls, after the initial binding occurred. The INotifyPropertyChanged interface is intended to be implemented by a datasource class that has properties that will be bound to. It provides a public event called PropertyChanged, which is registered with the data binding mechanism when you add the Binding to your Control. Your class then calls this event delegate in the desired property setters, after the new property value has been set. Make sure to pass "this" as the sender, and the name of the property in the event arguments object. Notice that the property name is provided as a String, which means that there is reflection involved. This will become relevant in Rule 4. Also note that there is an INotifyPropertyChanging interface, which exposes an event that is meant to be raised immediately before you apply the datasource change. This is generally less useful for databinding, but I include it here to save some poor soul the type of frustration I have recently endured.


Rule 4: If you implement INotifyPropertyChanged, don't include any explicit property change events ending with "Changed".


As I mentioned, the databinding mechanism uses reflection. And in so doing, it manages to outsmart itself. There is a very good chance you're going to run into a situation for which these databinding mechanisms aren't useful, and you'll have to implement your own explicit property change events on your datasource class. And of course, you're going to name these events in the style of "NameChanged", "AddressChanged", "HairColorChanged", etc. However, the binding mechanism things it's smart, and rather than just registering the INotifyPropertyChanged.PropertyChanged method, it will also register with any public event whose name ends with "Changed". And if you didn't happen to make your event follow the standard framework event signature pattern--that is, void delegate(Object sender, EventArgs e)--then you will get errors when the initial binding is attempted, as the mechanism attempts to register it's own standard-style delegates with your custom events, and you get a casting error.


I solved this one by following a crazy whim, but I also tried to verify the information online. All I could find was one old post buried in an obscure forum somewhere.


Rule 5: Don't bind to clickable Radio Buttons


I know how great it would be if you could just bind your bunch of radio buttons to an enum property. I really do. You think you're just going to hook up some Format and Parse events to translate back to your enum, and all will be well. It would be so darn convenient, if it actually worked. But WinForms just isn't cut out for this. For 3 full releases now (or is it 3.5 releases?), this has been the case. It's because of the event order, which is not something that MS can go switching up without causing thousands of developers to get really cheesed off.


The problem really comes down to the fact that unlike other controls' data properties, the Checked property of a radio button doesn't actually change until focus leaves the radio button. And as with all WinForms controls the focus doesn't actually leave the radio button until after focus is given to another control, and in fact not until after the Click event of the newly focused control has fired. The result of this, as it pertains to radio buttons, is that if you try to bind to them, the bound properties in your datasource will actually lag your radio buttons' visual state by one click. If you have just two radio buttons, the datasource will be exactly opposite the visible state, until you click somewhere else that doesn't trigger an action that references those datasource properties. Which can make this a really infuriating bug to track down. I almost thought I was hallucinating.


Now, in all honesty, it's possible to make it work. But it is the kludgiest kludge that ever kludged. Okay maybe it's not that bad... but it's a messy hack for sure. It takes a lot of work for something that really should already be available. As near as I can tell, the only way to solve this problem without giving up the databinding mechanism is to essentially make your own RadioButton control, with a property change and event order that is actually useful. You can either write one from scratch, or sub-class RadioButton and override all the event logic with custom message handling.


So....


There's the result of 3 weeks of frustration. I hope it helps someone else out there someday. I'll make list addendum posts if/when I come across any other mind-boggling flaws in the WinForms databinding model. And in the meantime, I welcome any additions or corrections that anyone is willing to contribute in the comments.

6 comments:

nicholas said...

Sounds about right--it's refreshing to encounter someone who has similar frustrations when using Windows Forms databinding on objects. It doesn't help that the databinding implementation is a bit baroque and that the sequence of events (OnPropertyChanged vs OnValidate, for example) can cause drastic differences in the way things work. (One of the most common questions I get is that "the save button is disabled" because they haven't unfocused a particular control--but switching to OnPropertyChanged could cause a Parse event to fail spectacularly and not let them leave the control. You can't win.)

I think more than a few developers end up ignoring databinding, which is a shame because it can save a lot of time with ErrorProvider and drudge work. Here's hoping that the situation has gotten better in WPF, though I won't be able to use that for quite some time.

Oliver said...

Coming from a Java world where the Swing Framework is so well designed with comprehensive support for the MVC design pattern I found the .Net WinForms library like a giant step back in time. While data binding provides some good facilities it is disappointing that it was not more universally applied and less buggy.

MArk said...

well. I too share the same frustration like the other two guys lamenting here. Importance of data binding is being ignored nowadays. You might be interested about the article about
data binding

Anonymous said...

thanks for summing up those rules. i also experienced quite some disappointment working with databindings in winforms :/

Adam Collings said...

Thanks. I was violating Rule 2. Your article made me realise my silly mistake.

Anonymous said...

I haven't found problems with #2--I've been doing the binding in the constructor, and using the default property of the control's DataBinding collection to retrieve the relevant binding (as opposed to using the return type from the .Add(param,param,param,param) method, or creating a binding and using .Add(binding)). Also, VB 2008 / .NET 3.5