TechEd 2008 notes: Implementing Objects for Data Binding
This, he advised us, was an advanced, specialized talk. It wasn’t about how to use databinding to create UIs; it was more about how to make your objects support databinding, especially in WinForms.
Implementing Objects for Data Binding
Rockford Lhotka
Principal Technology Evangelist
Magenic
His focus: how to make OO work under various Microsoft technologies. As time has gone, our lives have gotten much easier, but as you dig in, you find the warts and gotchas.
Data Binding
- ASP.NET Web Forms
- Simplest, least capable model
- WPF
- Interactive, but simple model
- Lacks full functionality
- Windows Forms
- Interactive model with high functionality
- Most complex model
- If you support Windows Forms, you (almost certainly) support everything else. (WPF is diverging a tiny bit — Rocky sees more of this than any of us will.)
Rocky maintains the CSLA.NET open-source project, which already does all the plumbing for databinding to work. He didn’t set out to become a databinding guru; he just found that it took a lot of time, and was worth sharing.
He has a friend who got fed up and wrote his own databinding framework. Rocky didn’t, because he thought Microsoft got really, really close in .NET 1.1. He thinks they did get better in .NET 2.0, and yet it still has holes.
This will deal with .NET 2.0+. Changes to databinding are largely stalled at this point, with little or none changed after 2.0, because MS is focusing on WPF, and the WPF databinding story is very good (and yet it still has holes).
Architecturally, the goal should be to have zero lines of code in the GUI. We’re not there, even with WPF (though we’re tantalizingly close).
- How rapidly does your database change? …Not very fast.
- How rapidly do your business rules change? …Some, but the big things change slowly.
- But UI changes all the time.
- Users don’t help. “This is too complex! Too much clutter! Can’t you make it into a wizard?” “This wizard is too awkward. Can’t you make it one page?”
- Users can’t make up their minds, and technology keeps changing.
- There’s no code more expensive than the code in the GUI.
Databinding is the main tool MS gives us to get rid of code in the GUI. It’s a very clear, interface-based abstraction. That’s why it’s so important.
Property Change Notification
- When your object changes, it should tell somebody.
- History: In .NET 1, MS tried to tell us not to worry, that they’d take care of it all: change it here, we’ll update it everywhere for you. But that doesn’t take care of indirect changes, like with calculated fields. So there was a convention for magically-named events that they would listen to; but they refresh ALL the fields, so if you do the Right Thing and fire four change events, all the GUI refreshes four times. So you want to work around that and only fire one, but there’s another wrinkle: databinding only listens to properties that are actually bound to controls, and the control has to be visible. Nothing wrong with having a size of 0×0, though. (I can already see the problems with tab order, and…)
- In .NET 2, MS formalized this with: (most of these will be in System.ComponentModel)
- INotifyPropertyChanged. Forces you to raise an event called PropertyChanged. Much more discoverable… but all the same idiosyncracies are true to this day.
- INotifyPropertyChanging. New with LINQ. Not used in databinding, at least not yet. Data context object in LINQ to SQL uses it.
- Force declaration of notification events
Field Refresh Issue
- If your property has a setter that does something.
- When you type into a text box and tab out of it, the value is pushed down into the object. If your setter does some rules, like uppercasing the value, the GUI will not display the changed value, even if you raise PropertyChanged?
- Windows Forms
- There’s an optimization.
- When they get PropertyChanged, they refresh all the fields except the one you just edited.
- Solve by handling event on BindingSource
- WPF
- Doesn’t refresh everything. Only refreshes the property that you said was changed.
- Same optimization: their assumption is that nobody writes code in the set block.
- Solve by always using a ValueConverter
Demo
- You must fire PropertyChanged in your property setter. Otherwise you can set the property and the GUI won’t refresh, even though the backing object is changed.
- So you shouldn’t bind automatic properties.
- If your property setter doesn’t save the exact value (i.e., if it does some validation or range checking), the GUI won’t see it.
- WPF binding:
Text="{Binding Path=Value1}" - Works fine unless the code is changed indirectly.
- WinForms workaround: hook an event on BindingSource and manually refresh the display.
- WPF workaround: Value converters (things that implement IValueConverter).
- Convert(…) and ConvertBack(…). Convert puts the value into the UI; ConvertBack puts it back into the object. This is how you e.g. format dates.
- It turns out that if you have a value converter, WPF databinding will always refresh the display. So you can make a value converter that just does
return value;from both methods. - IdentityConverter
- Change XAML to
Text="{Binding Path=Value3, Converter={StaticResource IdentityConverter}}" - So how does your UI developer know whether or not to use the IdentityConverter? A: They can’t know, so they should always have a value converter on every binding: either a real one to do formatting and parsing, or an identity converter.
- WinForms binding:
this.dataBindingSource.BindingComplete += handler, and handler doese.Binding.ReadValue();. - dataBindingSource is a .NET 2.0 DataBindingSource component. He actually dragged one of his objects onto the form, and the designer created the DataBindingSource for him.
- DataBindingSource collects mystical stuff that was already there, and makes it more visible.
- CSLA puts this into an extender control; otherwise you need to do this in every screen.
Events and Serialization
- Event handlers cause forward references
- Your object ends up referencing the event handler
- BinaryFormatter follows / serializes all references
- Unless marked as NonSerialized
- Many event handler objects aren’t serializable (Form, Page, etc.)
- Marking an event NonSerialized does nothing
- You need to mark the backing field created by the compiler
- NetDataContractSerializer in WCF is very similar
- BinaryFormatter serializes private fields, not properties (by default)
- What if a field references another object?
- Serializes that other object
- What if the field points to an object that can’t be serialized?
- Boom (with reverb). SerializationException. That’s what you would expect.
- Events: if the form hooks your event (e.g. because you’re doing databinding), you have a delegate field behind the scenes that refers to the form. So you can’t serialize it.
- NonSerialized attribute says “don’t bother trying to serialize it”, but an event is not a field.
- Solution: declare your event as a custom event, and manually define the backing field. (Or in C#, just put
[field: NonSerialized]on the event. Rocky doesn’t like that syntax and would prefer to add the extra dozens of lines of code for every event; I’d go for the simplicity of the one-line fix.)
Lists and Collections
- Windows Forms or WPF
- IBindingList. ListChanged event, searching, sorting.
- BindingList<T>. Already implements all the interfaces necessary for databinding to Just Work in WinForms.
- WPF only
- INotifyCollectionChanged. Subset of IBindingList: more like IPropertyChanged. “I will notify you if the list changed.”
- ObservableCollection<T>, which implements INotifyCollectionChanged but not all the other databinding stuff.
- IEditableCollectionView
- So you just implement both, right? But WPF honors BOTH! So if you implement both, and then remove an item from a list, the UI removes two items.
- Implement one or the other, not both.
- Any behavior in the WPF one that’s not in BindingList? A: No.
- Any benefit to using ObservableCollection<T> in WPF? A: If you’re already using BindingList, no.
- BindingList<T> requires that the class have a public default constructor. What if your object doesn’t have one, for any number of reasons?
- Add a class that descends from BindingList<Data>, sets AllowNew from the constructor, and overrides AddNewCore().
- If you set AllowNew to true, you must override
AddNewCore(). - Inside AddNewCore, you must do three things:
- Create a new instance.
- Add the object to the collection.
- Return it.
Validation Error Support
- IDataErrorInfo
- This is how your object can tell databinding whether each property is valid, and whether the object at large is valid.
- Simple, but of course idiosyncratic: if you do it the wrong way, life is not so much fun.
- Interface makes you add two properties: Error, and an indexer based on column name (both return a string). If you return an empty string or a null, it means you have a problem and the string is the human-readable reason why.
- Every time a field changes, not only do the fields get refreshed, so does the error info. So it can get called over and over again. Be very conscious of performance: you can’t do anything here that takes any time.
- Recommendation: check your rules when you set the property.
Supporting In-Place Grid Editing
- IEditableObject, which is a mess
- BeginEdit (”I might need you to undo yourself, so take a snapshot of your state”)
- EndEdit may be called (”Commit the changes”)
- CancelEdit (”Revert”)
- Ridiculously idiosyncratic.
- The binding system may call BeginEdit many times; you should only honor the first.
- They may never call EndEdit. If so, you should sort of assume it was called.
- Best way to handle this when you’ve got master/detail bindings: hook CurrentChanged on the master BindingSource, and call EndEdit() on the detail.
All code will be available via TechEd and via Rocky’s Web site.
June 9th, 2008 at 7:26 am
[…] has posted very detailed notes from TechEd 2008: #1 ~ #2 ~ #3 ~ #4 ~ #5 ~ #6 ~ #7 ~ #8 ~ #9 ~ […]