3
votes

Ok, following on from yesterday I've added a new layer of complexity. We still have a theoretical Model class, ViewModel and View. This time my Model has a Threading.Timer (Chosen specifically to get timer callbacks on the "wrong" thread.

The Model has an ObservableCollection. The Timer callback adds items to the collection.

The ViewModel simply passes the collection to the View which contains a listbox bound to the collection.

This doesn't work.

The model also exposes a string which is updated in the same timer callback.

This too is exposed via the viewmodel and bound to a TextBox.

This does work.

I've seen hints in my googling that updating collections doesn't make INotifyCollectionChanged work as expected. I get a total implosion, not even an exception, just immediate termination of the application.

So there are two questions:

One relates to our discussion yesterday. I'm using INotifyPropertyChanged and ObservableCollections in my Model because they are the thing upon which the view does work. It still makes sense to me to use these mechanisms to notify my viewmodel, or what ever that the underlying model has changed. So how do I deal with updates occuring on a different thread?

Second, what is happening that makes INotifyPropertyChanged work with the binding? I'm binding a string property to a DependencyProperty called Text, so is it the DependencyProperty system that marshals my change back to the UI thread? Edit: And can I rely on it, i.e. does it do this because they expect me to talk to it cross-thread, or is it just a catch all that I shouldn't rely on?

The ListBox is bound through ItemsSource="{Binding ObsCollection}". When this crashes the application. Actually, at first I started the timer when the Model was created which happened when the Window's DataContext was set, so it would actually bomb Visual Studio...

Thanks

2

2 Answers

2
votes

WPF controls have thread-affinity, what this means is that their properties can only be modified from the UI thread. Therefore, if you update a property value from a Timer (other than a DispatcherTimer), you will have to marshal this update onto the UI thread. This is perfomed via the dispatcher:

Application.Current.Dispatcher.BeginInvoke(
  DispatcherPriority.Normal,
  new Action(() => // update your control here));

The databinding framework does not ensure that updates are marshalled onto the UI thread, therefore, if you update your model from a different thread, this will cause issues. therefore you need to use the same pattern as above. In other words, if you are adding objections to your observable collection, this add must be performed via the Dispatcher.

2
votes

This problem is quite prevalent in WPF. I think the best option is to have your own ObservableCollection<> subclass that takes care of dispatching event notifications to the UI thread automatically.

And since the wheel has already been invented, I 'm going to simply refer you to the answer to this question: ObservableCollection and threading.