|
This is more of a guidance question than a how to question, and some reflections on the whole MVVM pattern in the WPF framework. We built a test UI to test a service that we are working on. The service is highly threaded (makes use of many threads), and when building the WPF UI we ran into some issues. At the time of writing this post, the UI works fine, but I am a little concerned that I have not read any forum posts or articles that relate to our MVVM implementation. I will use a database of egg timers to design my example. Example Setup ------------------- Imagine that we have a class EggTimer which would simply count down time until the alarm should sound. These timers will run on their own thread because (in our stupidity) our implementation of the egg timer is a class that sleeps for 1 second until the countdown timer reaches zero. (p.s. this is just a fake example to illustrate our MVVM design, so don't try and suggest how to make a better egg timer please ). Our service model (ServiceModel ) would manage an ObservableCollection<EggTimer> and allow new EggTimer 's to be added to the list. Once an egg timer has sound its alarm (EggTimerElapsed event?) it will be marked for cleanup. Our service model only allows 10 egg timers to run at a time, so it must manage which egg timers get processed in a worker thread. the service model also has a cleanup thread that prunes timers that have run to completion and are no longer necessary to keep around in the list. To recap, service model contains a ObservableCollection<EggTimer> which runs 2 threads (worker and cleanup). Each EggTimer counts down on its own thread and fires an event when finished. service model starts up to 10 egg timers at once, and tries to keep 10 going at all times until the list empties. service model cleans up timers that have finished. Our UI is just a simple list that would be data bound to the collection, showing the timer name, time left, and a progress bar. Design Details ------------------ What we quickly found is that when databinding to a collection, the collection must now always be modified from the UI thread. Not a big problem, but requires nasty dispatch code to be added to the model. To me, the model should not have to worry about such non-sense, as long as list modifications are thread safe why should we care which thread modifies the list. Our solution was to replicate the collection in a view model class (i.e. ServiceViewModel ). By this i mean we have a the service view model class, it contains a handle to the UI dispatcher, and a private observable collection that would always be synchronized to the ServiceModel via collection change notifications. The collection items are the exact same reference, so any changes occuring on the timers are reflected in databound components of the UI list's item columns. All view model list modifications are dispatched properly when a model collection change notification is received. Finally, a static singleton (for the view model) is used for the binding source, and a public property that returns the view model list is used for the binding path. We currently only perform 1-way binding, since there is no UI interaction (just a visualization). However, list interaction could be achieved by listening to the view model list's collection change notifications and replicating the changes in the model list. Since the EggTimer's are simple, we don't have to worry about UI dispatching for their changes, but if they contained lists themselves that would be modified on a non-UI thread after data binding, then we would have to create a view model for them as well and replicate changes in the same fashion that we do for the service model (for the sake of simplicity however, this issue is not necessary for this discussion). This setup works, but i am sure that there are holes in the design. I would love to hear criticisms of this design and suggestions for improvement. The primary problem that we deal with in this example is how to effectively bind to a list which is modified on a non-UI thread, and whose list items are also modified on a non-UI thread. The list is always changing so constant synchronization between the UI and model is critical. Thanks to all who contribute. |