8

I must be retarded with searching, because here's another seemingly common problem that I haven't been able to solve.

Here's my problem -- I am using WPF and MVVM, and I have a statemachine that executes in the model. If an error occurs, I need to pass information up to the ViewModel to display the error. This part seems to work okay. When the user clicks the desired behavior, the code in the model continues, and looks at the object the user interacts with to determine what to do next.

The problem is that the model needs to reload a file, which updates the GUI with the contents of said file. Because the model is executing in a thread, you can imagine what I'm going to ask next -- how the hell do you synchronize with the GUI properly? In MFC, I would have used either SendMessage or PostMessage to accomplish the GUI update.

I've read articles for WinForms that suggest using InvokeRequired to automatically call BeginInvoke if necessary. I actually didn't know that BeginInvoke would accomplish what I wanted, so I was encouraged to learn this.

How do I actually call BeginInvoke from my model? Does this method even apply to WPF? I went ahead and implemented a delegate and then called Invoke, but I get the same error that tells me the collection can't be modified from this thread. I also tried BeginInvoke for the hell of it, but I assume that also wouldn't work because it would just launch from a different thread anyway.

Confused. If I have missed something really obvious that's been posted about all over the internet, go ahead and give me a verbal lashing, I probably deserve it.

EDIT - I should probably also add that I am looking for something other than a timer or BackgroundWorker-based solution, unless that's the only way to solve this in WPF / MVVM. Also, I wonder if any of the MVVM toolkits would have facilities for this sort of thing already...

Dave
  • 14,618
  • 13
  • 91
  • 145
  • To clarify: I assume that you were simply calling Invoke() on the delegate directly rather than doing Dispatcher.Invoke()? The first will simply execute the delegate on the same thread, the second will marshal the call to the Dispatcher thread (which may or may not exist depending upon what type of application is consuming your ViewModel). – lesscode Mar 09 '10 at 17:57
  • Wayne, you're right. I was just roughly following the article, and I wonder if the fact that it was for WinForms is why their example works? Anyhow, I think Franci was right and I need to pass in a Dispatcher by any means necessary. :) Thanks for your time. Hopefully I can get this resolved soon. – Dave Mar 09 '10 at 19:46
  • Its possible you could leverage MVVMLights Messenger class to send a message to the GUI without going through eventing. You would still have to go through the Dispatcher if the Code Behind touched any of the actual UI. I am a little confused, since Databinding already goes through the Dispatcher why this is an issue. If your Thread updates a bound property on the View, it should automatically be marshaled to the UI without a problem. – Agies May 27 '10 at 03:00

3 Answers3

8

If you want to schedule some work from a background thread to the UI thread in WPF, use the DispatcherObject. Here's a nice article on how to Build More Responsive Apps with the Dispatcher.

Update: Note that if you use an event to send notifications from the Model to the ViewModel, you still need to switch to the UI thread somewhere. Whether that switch should be in the Model or the ViewModel is a good design discussion, but it's orthogonal to your question.

The event will be raised on the corresponding Dispatcher thread. Since you need to get to the UI thread, you need to use a Dispatcher that is created on the UI thread. The easiest way to get one is to use the DispatcherObject.Dispatcher property on one of the UI elements. The alternative is to create one in your Model or ViewModel. If you are a design purist, I would suggest you create the Dispatcher in your Model and dispatch the call back to the UI thread before you raise the event to which the ViewModel is listening. This way all the thread switching and management is contained in your Model and the ViewModel works as a single-threaded on the UI thread only.

Franci Penov
  • 74,861
  • 18
  • 132
  • 169
  • how funny, I was just reading that article and was thinking about the DispatcherObject. However, Wayne brings up a good point, and I should look into using an event to deal with this. – Dave Mar 09 '10 at 17:48
  • I think I'm going to have to give the DispatcherObject a whirl. Your point is valid that regardless of the GUI architecture, I still need a way to synchronize with the main thread. Can you explain why Wayne's example above works for properties? I stepped through just to prove to myself that it really does everything from a different thread, and it does, yet it still works as one would want it to. – Dave Mar 09 '10 at 19:36
  • I should have refreshed my browser before commenting. Anyhow, thanks for the advice. It's an interesting idea, if I understand you correctly. Instead of taking the Dispatcher property from the View and passing it down (which seems to violate MVVM anyway), you recommend creating a reference to a Dispatcher in my Model, and then essentially, via a callback interface (which gets passed to the Model's constructor), the Model can request the View's Dispatcher? I assume that then the View should also pass a reference to the ViewModel when it is created? – Dave Mar 09 '10 at 19:44
  • Problem solved. I just have the controller get a reference to the View's dispatcher (which is passed down from View -> VM -> Model), and then everything work. Thanks, Franci, and thanks Wayne for trying to help me out with this. – Dave Mar 09 '10 at 20:03
4

I think that your ViewModel really shouldn't know anything about the View, including whether or not it's a WPF UI, or whether or not that UI even has the concept of a Dispatcher thread, so the red flag should fly as soon as you start writing code in your ViewModel that attempts to CheckAccess() or InvokeRequired in order to marshal some code to the UI thread. Instead I'd have the model raise an event that the View can listen for and update itself accordingly, or have the ViewModel expose a property (eg. bool FileIsLoading) that the View simply binds to in order to detect and display what the model is doing asynchronously, and it's the ViewModel's responsibility to ensure that the value of that property is accurate.

For example:

public partial class MainWindow : Window {
    private ViewModel _model = new ViewModel();

    public MainWindow() {
        InitializeComponent();
        DataContext = _model;
    }

    private void Button_Click(object sender, RoutedEventArgs e) {
        _model.Run();
    }
}


<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Click="Button_Click"
                Content="Run"
                IsEnabled="{Binding IsIdle}" />
    </Grid>
</Window>



public class ViewModel : INotifyPropertyChanged {

    private bool _isIdle = true;

    public bool IsIdle {
        get { return _isIdle; }
        set {
            _isIdle = value;
            OnPropertyChanged("IsIdle");
        }
    }

    public void Run() {
        ThreadPool.QueueUserWorkItem((state) => {
            IsIdle = false;
            Thread.Sleep(10000);
            IsIdle = true;
        });
    }

    #region INotifyPropertyChanged Implementation

    protected void OnPropertyChanged(string propertyName) {
        PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
        if (propertyChanged != null) {
            propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}
lesscode
  • 6,221
  • 30
  • 58
  • That's absolutely true, the lower level stuff shouldn't know anything about the higher level stuff. I try to keep that in mind when I working on my app. In this particular case, I totally didn't even notice that I was modifying things in the ViewModel, and it could be because this particular component I am working on has the Model and ViewModel in one class (to save time with the alpha). I think the event is the way to go, because your suggestion about having the VM expose a property is exactly what I'm doing, but ultimately I can't update that property from the thread without an event. – Dave Mar 09 '10 at 17:53
  • Well, technically even exposing/changing a property is raising an event -- you'd need to make sure that your ViewModel implements INotifyPropertyChanged to inform the WPF databinding plumbing that your property had changed. – lesscode Mar 09 '10 at 18:03
  • Databinding is fine, I've had that working for a while. So maybe I should just confirm this -- your suggestion for an event-based solution will work when my *worker thread* raises the event, and my *ViewModel* has registered the event handler? – Dave Mar 09 '10 at 18:09
  • The worker thread is initiated from the ViewModel? In that case the View is not involved, right? Your problem is "how do I tell the View that the background work finished?" For that I don't see why a simple (data-bound) property in the ViewModel wouldn't suffice. Am I missing something? – lesscode Mar 09 '10 at 18:12
  • The worker thread is indeed initiated from the ViewModel. Here's how it goes -> user clicks button, ICommand is executed and calls into the VM, which then calls into the Model. The model spawns a worker thread and does its thing. The model eventually fails and needs to recover. The act of recovery then requires that the GUI get refreshed. I want the model to somehow be able to asynchronously signal the GUI to update, and this is currently attempted by having the ViewModel update an ObservableCollection that is databound to the View. BTW, the event isn't syncing with the main GUI thread. – Dave Mar 09 '10 at 18:19
  • >>The act of recovery then requires that the GUI get refreshed. In WPF, you don't (shouldn't want to) control this. The databinding plumbing will take care of it as long as the bound items (ViewModel, objects) are INotifyPropertyChanged/INotifyCollectionChanged). I'll post a sample above. – lesscode Mar 09 '10 at 18:34
  • Note that any event will still be raised on the background thread, so you still need to figure out how to switch to the UI thread to do the work there. Whether that switch should be in the model or the viewmodel is a good design discussion, but it's orthogonal to your question. – Franci Penov Mar 09 '10 at 18:34
  • @Wayne: your approach seems to work for properties, but it does not work for ObservableCollections. I modified your example (thanks for posting it!!!) to add an ObservableCollection, and modifying your Run() method accordingly results in the same crash I'm trying to work around. This is pretty interesting (to me at least) :) – Dave Mar 09 '10 at 19:35
  • Right - I'd forgotten that although WPF supports INotifyPropertyChanged dispatch, it doesn't yet support INotifyCollectionChanged dispatch. I looked back at some old code to see how I was doing this in my apps, and I'm using something akin to this: http://www.julmar.com/blog/mark/2009/04/01/AddingToAnObservableCollectionFromABackgroundThread.aspx – lesscode Mar 09 '10 at 21:01
1

I've got another approach that seems to work, and I just wanted to throw it out there to get some comments (if anyone is even reading this question anymore!).

I started to use the MVVM Light Toolkit's Messenger class, and it seems to work really well for me. For example, let's take the ProgressBar as an example. I registered two messages with my ViewModel for setting the progress value and progress maximum. Then in my model, as it sets up the tasks and overall process, it sends these messages. When the VM receives the messages, it just updates databound values, and my GUI updates automatically! It's super duper easy, but I was wondering what you all thought about this approach. Is anyone else doing this without incident?

Dave
  • 14,618
  • 13
  • 91
  • 145