Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

True separation of code and presentation while using the Dispatcher

Tags:

c#

.net

wpf

In my WPF try to separate my classes logic from any interface related data and only supply ObservableCollection properties for binding.

Problem is, when I access those binded OCs from other threads, I am required to do so through the dispatcher. The result is that I am required to add many Dispatcher.Invoke() calls, hidden inside my classes, whenever one of the methods attempts to update the OCs.

How can I do that in a more clean and separated way, so the dispatcher calls be abstracted away from my methods?

like image 902
Saul Avatar asked Feb 10 '11 10:02

Saul


3 Answers

I don't have a silver bullet. But if you are certain and ready to take the responsibility of implicit UI delegation, you can always inherit from ObservableCollection, override methods and dispatch all requests to UI.

But the following code makes me scary:

// somewhere in thread pool:
for(int i = 0; i < 1000; i++)
{
   _dispatcherAwareCollection.Add(i);
}

It seems innocent, but under the hood it blocks calling thread 1000 times. Alternatives might be your specific BulkXXX() methods, that will delay notification until all elements are processed. This solution is not perfect either, since you wanted an abstraction that could let you seamlessly swap collections, but BulkXXX() methods are very specific to new collection.

like image 106
Anvaka Avatar answered Oct 16 '22 05:10

Anvaka


Option 1

I think you should look into a better separation of your code using the MVVM pattern, if you aren't familiar with it, I highly suggest to see the following video as it explains exactly what you're looking for.

Specifically, however, in your case you should have the model class with regular collection (e.g List) on which you do all the work in the threads. Your ViewModel should contain the ObservableCollections and connect loosely with the collections that exist in the model, e.g, you can choose to subscribe via an event from your ViewModel to a certain update logic in your model. You will STILL need to use Dispatcher to update the OC, but you will only need to do it once.

Option 2

You can instead just use the solution described here. Basically, he created a new derived class from OC that allows you to dispatch changes from the code automatically without you ever needing to update the dispatcher yourself.

like image 39
VitalyB Avatar answered Oct 16 '22 05:10

VitalyB


The common approach is to have a Dispatcher property on your view model (probably in a base class for all view models) that can be injected outside. It is OK to have it in a view model because view model SHOULD be aware of UI concepts, but it should not be aware of particular view (layout, controls, etc.) and certainly it should not have a reference to the view.

What you can do is you can make it easier to dispatch your code to the Dispatcher thread by creating a helper or a service that will abstract the dispatcher away. For example, you can create a helper like this:

public class AsyncHelper
{
    public static void EnsureUIThread(Action action) 
    {
        if (Application.Current != null && !Application.Current.Dispatcher.CheckAccess()) 
        {
            Application.Current.Dispatcher.BeginInvoke(action, DispatcherPriority.Background);
        }
        else 
        {
            action();
        }
    }
}

And whenever you need to update you observable collection, you wrap you code in that helper method:

AsyncHelper.EnsureUIThread(() =>
{
    // Update you observable collections here
});

OR, you can go further and use AOP (e.g. PostSharp) to specify declaratively (using attributes) that a method should be executed in the UI thread.

And finally, please note that you have to dispatch only collection updates to the UI thread. Usual properties can be safely updated from a background thread. The updates will be dispatched to the UI thread automatically by the binding mechanism. Probably in future versions of WPF updates to a collection from a background thread also will be supported.

like image 36
Pavlo Glazkov Avatar answered Oct 16 '22 05:10

Pavlo Glazkov