views:

4946

answers:

4

In a WPF app that I'm writing using the MVVM pattern, I have a background process that doing it's thing, but need to get status updates from it out to the UI.

I'm using the MVVM pattern, so my ViewModel knows virtually nothing of the view (UI) that is presenting the model to the user.

Say I have the following method in my ViewModel:

public void backgroundWorker_ReportProgress(object sender, ReportProgressArgs e)
{
    this.Messages.Add(e.Message);
    OnPropertyChanged("Messages");
}

In my view, I have a ListBox bound to the Messages property (a List) of the ViewModel. OnPropertyChanged fulfills the role of the INotifyPropertyChanged interface by calling a PropertyChangedEventHandler.

I need to ensure that OnPropertyChanged is called on the UI thread - how do I do this? I've tried the following:

public Dispatcher Dispatcher { get; set; }
public MyViewModel()
{ 
    this.Dispatcher = Dispatcher.CurrentDispatcher;
}

Then adding the following to the OnPropertyChanged method:

if (this.Dispatcher != Dispatcher.CurrentDispatcher)
{
    this.Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(delegate
    {
        OnPropertyChanged(propertyName);
    }));
    return;
}

but this did not work. Any ideas?

+10  A: 

WPF automatically marshals property changes to the UI thread. However, it does not marshal collection changes, so I suspect your adding a message is causing the failure.

You can marshal the add manually yourself (see example below), or use something like this technique I blogged about a while back.

Manually marshalling:

public void backgroundWorker_ReportProgress(object sender, ReportProgressArgs e)
{
    Dispatcher.Invoke(new Action<string>(AddMessage), e.Message);
    OnPropertyChanged("Messages");
}

private void AddMessage(string message)
{
    Dispatcher.VerifyAccess();
    Messages.Add(message);
}

HTH, Kent

Kent Boogaart
That did it, with one other minor modification - I changed my List<string> to an ObservableCollction<string> and it works like a charm. Thanks!
Adam Barney
+2  A: 

I had a similar scenario just this week (MVVM here too). I had a separate class doing its thing, reporting back status on an event handler. The event handler was being called as expected, and I could see the results coming back right on time with Debug.WriteLine's.

But with WPF, no matter what I did, the UI would not update until the process was complete. As soon as the process finished, the UI would update as expected. It was as if it was getting PropertyChanged, but waiting for the thread to complete before doing the UI updates all at once.

(Much to my dismay, the same code in Windows.Forms with a DoEvents and .Refresh() worked like a charm.)

So far, I've resolved this by starting the process on its own thread:

//hook up event handler
myProcess.MyEvent += new EventHandler<MyEventArgs>(MyEventHandler); 

//start it on a thread ...
ThreadStart threadStart = new ThreadStart(myProcess.Start);

Thread thread = new Thread(threadStart);

thread.Start();

and then in the event handler:

private void MyEventHandler(object sender, MyEventArgs e) { 
....
Application.Current.Dispatcher.Invoke(
       DispatcherPriority.Send,
       (DispatcherOperationCallback)(arg =>
       { 
         //do UI updating here ...
        }), null);

I'm not recommending this code, since I'm still trying to understand the WPF thread model, how Dispatcher works, and why in my case the UI wouldn't update until the process was complete even with event handler getting called as expected (by design?). But this has worked for me so far.

I found these two links helpful:

http://www.nbdtech.com/blog/archive/2007/08/01/Passing-Wpf-Objects-Between-Threads-With-Source-Code.aspx

http://srtsolutions.com/blogs/mikewoelmer/archive/2009/04/17/dealing-with-unhandled-exceptions-in-wpf.aspx

Jeffrey Knight
A: 

I handle the BackgroundWorker.ReportProgress event outside of my view model and pass the actual BackgroundWorker instance and the ViewModel into my class which defines the asynch method(s).

The asynch method then calls bgWorker.ReportProgress and passes a class which wraps a delegate as the userstate (as object). The delegate I write as an anonymous method.

In the event handler, I cast it from object back to the wrapper type and then invoke the delegate inside.

All this means that I can code UI changes directly from the code that's running asynchronously, but it just has this wrapping around it.

This explains in more detail:

http://lukepuplett.blogspot.com/2009/05/updating-ui-from-asynchronous-ops.html

Luke Puplett
+2  A: 

I REALLY like Jeremy's answer: Dispatching In Silverlight

Summary:

  • Placing Dispatcher in ViewModel seems inelegant

  • Creating an Action<Action> property, set it to just run the action in the VM constructor

  • When using the VM from the V, set the Action property to invoke the Dispatcher
Mike Graham