Earlier I lamented my fate as a GUI developer with DevExpress GUI widgets. After a few hours of repeated head/wall interfacing, I found the problem, and it’s so maddening I must now vent.
Most of my business objects are displayed in the GUI using WinForms and DevExpress data binding. If I have a list of objects to display in a grid or list or, in this case, a calendar, I use lists derived from System.ComponentModel.BindingList<T>, which implements all the plumbing, like IBindingList, plus general-purpose typed container stuff.
Using one of these lists as the DataSource for WinForms or DevExpress controls Just Works. However, as mentioned before, the scheduler control doesn’t switch to the UI thread when handling ListChanged events, so when I load my list in a background thread it crashes the app. Rather than post to the support forums and wait until the next dot release, I just whipped up BraindeadBindableListWrapper, which implements IBindingList and delegates all but the ListChanged event to the IBindingList instance it wraps. For the ListChanged event, it intercepts the event from the wrapped list, switches to the UI thread, then passes the event on to whoever’s listening (typically the Scheduler control).
However, when I use this wrapper, none of my events show up. It wasn’t until a few hours into the problem that I realized the wrapper was the cause; DevExpress doesn’t throw an exception, or issue an error; it just silently ignores the properties on my event objects, and creates one subjectless, 1 Jan 01 event for each event I have.
Out of desperation, after trying a multitude of other stupid-but-doesn’t-hurt-to-try hacks, I added an ITypedList implementation to BraindeadBindableListWrapper, even though my BindingList<T>-derived lists that work fine don’t implement that interface. Shockingly, it worked fine.
I then began spelunking around the DevExpress source code (the source license for DevExpress is really the only thing between me and a psychotic episode), and found the spaghetti-like logic that teases out properties for list items.
The problem, ultimately, is that the DevExpress team aren’t aware of/don’t shit a shit about the principle of Least Surprise. Under this principal, framework programmers make design decisions with the intention of limiting the surprise experienced by the programmers using the framework. For example, a method that saves a file which conforms to the Principal of Least Surprise would simply save the file, while a non-conformant method might validate or flush buffers or do some other side-effect-having bit as well.
In DevExpress’s case, they want their data binding code to tease out the properties of the items in a list under any possible circumstances. This leads to a fairly arcane bit of heuristics, which include:
ITypedList impl, and using that if foundthis[] in C#), and using the public properties of the return type of the indexer if foundThat feeling of dull nausea in your stomach is a normal response to this stimuli.
This has a lovely side-effect: any generic List<T> or BindingList<T> list will Just Work, since the generic containers happen to provide a typed indexer. Similarly, any ITypedList impl will work. What’s more, an IBindingList that has at least one item in it will also work, since DevEx will deduce the properties from the first item it finds.
However, in a fit of maximal surprise, an empty IBindingList that is populated after binding will not work, since the heuristic doesn’t have any information with which to determine the properties of the items in the list at binding time.
So, here we have two shitty things:
Binding logic that uses a non-obvious, non-documented heuristic that at first blush appears to work/not-work with two equivalent implementations
Binding logic that isn’t smart enough to evaluate properties on an item-by-item basis
So, if my BraindeadBindableListWrapper either implements ITypedList or has a typed accessor, it’ll Just Work. A violation of the principle of Least Surprise if ever there was one. DevEx, I want my Saturday night back!