The suggested duplicate thread did not address my question
So it is my understanding that a WPF application handles everything UI related, button presses, updates to observable collections through the dispatcher thread, which can be called with Application.Current.Dispatcher.Invoke(Update UI element here
), while changes to models and data can be handled by background threads normally.
What I don't understand is why you need to call the dispatcher for say updating an observable collection Bound to a Combobox, but yet when I want to update the progress bar or text in a textbox or if a button is enabled I don't need to call the dispatcher. Everything I read states that the dispatcher is used for handling and updating the UI. Are textboxes, the status of progress bars, and whether or not a button is enabled not see as UI?
What is the difference between an Observable collection and text/progress bars that makes calling the dispatcher not required?
CodePudding user response:
Almost any call to a WPF UI element should happen on the main thread of the application. This is usually done through the methods of the Dispatcher associated with this thread. The dispatcher can be obtained from any UI element, but usually it is obtained from the application: Applicatiion.Current.Dispatcher.
If you do not assign values directly to the properties of UI elements, but use bindings for this, then the bindings mechanism has built-in marshaling of value assignment into the flow of the UI element. Therefore, the INotifyPropertyChanged.PropertyChanged event can be raised on any thread.
When the observable collection changes, the INotifyCollectionChanged.CollectionChanged event is raised. It is not provided for automatic marshaling to the UI thread. But the collection can be synchronized with the BindingOperations.EnableCollection (...) method. Then it can be changed (using synchronization) in any thread. If such synchronization has not been performed, then it can be changed only in the UI thread.
In addition to these events, there is also ICommand.CanExecuteChanged. There are no additional marshaling or synchronization methods provided for it. Therefore, it can be raised only in the UI thread. This is usually built into the WPF implementation of ICommand and the programmer doesn't need to worry about it. But in simple (mostly educational) implementations, there is no such marshaling. Therefore, when using them, the programmer himself must take care of the transition to the UI thread to raise this event.
So basically in MVVM practice you no matter what you would have to use a dispatcher to use
BindingOperations.EnableCollectionSynchronization(fooCollection, _mylock));
correct?
Yes. For the application of the collection, you understood correctly.
Here only the problem of separation of the View and ViewModel functionality arises. You can only call "EnableCollectionSynchronization" on the View or on the main UI thread. But you are implementing the collection in the ViewModel. Also, in order not to clog up memory, when deleting a collection (replacing it with another, clearing the bindings that use it, replacing the ViewModel instance, etc.), you need to delete the created synchronization using the "DisableCollectionSynchronization (collection)" method.
In this regard, if a single instance of the ViewModel is used throughout the application session, then using "EnableCollectionSynchronization ()" is the simplest solution.
Example:
public class MainViewModel
{
public ObservableCollection<int> Numbers { get; }
= new ObservableCollection<int>();
protected static readonly Dispatcher Dispatcher = Application.Current.Dispatcher;
public MainViewModel()
{
if (Dispatcher.CheckAccess())
{
BindingOperations.EnableCollectionSynchronization(Numbers, ((ICollection)Numbers).SyncRoot);
}
else
{
Dispatcher.Invoke(()=>BindingOperations.EnableCollectionSynchronization(Numbers, ((ICollection)Numbers).SyncRoot));
}
}
}
But if many VM instances are used, with mutual nesting and dynamic addition and removal (for example, this can be the case when implementing a tree and viewing it in a TreeView), then using "EnableCollectionSynchronization ()" becomes not trivial. If you do as I showed in the example above, then due to the fact that the reference to the synchronization object, to the collection, will be saved, they will not be deleted from memory and, accordingly, unnecessary instances of ViewModel will not be deleted. And this will lead to a memory leak. Therefore, in practice, marshaling of collection changes to the UI thread is often used.
It is also possible to embed in the INotifyCollectionChanged implementation, as well as marshaling to the UI thread, and calling "EnableCollectionSynchronization () / DisableCollectionSynchronization ()" while subscribing / unsubscribing to the CollectionChanged event. The implementation of the ObservableCollection does not have this, but there are custom implementations of the observable collections in various packages where the similar is implemented. And their use frees the programmer from creating routine, specific, repetitive code.
Unfortunately, I cannot tell you exactly what the package contains the required implementation. I prefer to use my own implementations.