|Things I've Learned About WPF, Part 1
||[Dec. 31st, 2008|01:42 am]
If your binding expression doesn't resolve, while it doesn't crash the application (it just shows nothing instead), it does report the error to the Output window in Visual Studio. However, if you're binding to a collection and then turning around and reinstantiating the collection, you won't get an error. After all, you didn't add anything to the collection the UI was bound to, so it didn't have anything to show you, right?
ObservableCollection<T> is read-only on any thread but the UI thread. This is recognized by the BCL team and there's a workaround posted on Beatriz Stollnitz's blog (relevant entry) that delegates requests to the UI thread when they're made from a different thread, but there are a few problems with it. She explicitly points out that the code isn't tested or the design bugs fully beaten out, so this wasn't entirely surprising.
The first problem I encountered is that BeginInvoke requests that the UI thread handle the request some time in the future, but if you're trying to sequentially add items to the collection using Insert, the Insert call might return before the collection is actually modified. This can result in you trying to add an item at Count + 1 (assuming the Count's been updated), and having it blow up in the Collection code with IndexOutOfRange. Well, I'm using a worker thread because I'm calling out to the web, something I can't afford to do on the UI thread due to UI responsiveness. Thus, the worker thread blocking on the UI thread for a dispatch at the highest priority is probably not going to cause any problems without something already being wrong, so I took the cheap way out on this issue and just switched to using Invoke, making the requests synchronous.
The second problem I encountered was when I tried cleanly shutting down the program. I sent a message to the worker thread and shut down the UI thread... which promptly resulted in the dispatcher I was dispatching requests to going away. The object still exists, it's just shut down, so the requests are basically dumped into /dev/null. That resulted in another IndexOutOfRange if I tried to shut down the program while I was shoving several items into the collection. Again lazily, I just started checking for the signal more often. I think a better answer would be to have the collection throw an exception if it's accessed after the dispatcher begins shutting down, then decorating calls to the collection with try/catch and terminating if I encounter the "DispatcherNoLongerAvailable" exception.
Note: One major design decision I've made is that there is ONE UI thread and ONE worker thread. The UI thread never blocks on the worker thread. That simplifies a bunch of these issues.
But what's really awesome is that after I'd figured out all of these problems, I moved the first display into a TabControl/TabItem, added a new collection with new objects and its own saving/loading logic, mimicked the display logic from the first display into a new TabItem, and had a functioning shiny new pane in about 30 minutes.
I'm worried about implementing the modal window to add data, but I'm sure I'll figure it out.