Update UI from ViewModel class (MVVM pattern) in WPF

15,544

Solution 1

If it takes a long time, consider using a separate thread, for example by using a BackgroundWorker, so that the UI thread can stay responsive (i.e. update the UI) while the operation is performed.

In your Save method, you would

  • change the UI (i.e. modify some INotifyPropertyChanged or DependencyProperty IsBusySaving boolean which is bound to your UI, hides the Save button and maybe shows some progress bar with IsIndeterminate = True) and
  • start a BackgroundWorker.

In the DoWork event handler of your BackgroundWorker, you do the lengthy saving operation.

In the RunWorkerCompleted event handler, which is executed in the UI thread, you set IsBusySaving to false and maybe change other stuff in the UI to show that you are finished.

Code example (untested):

BackgroundWorker bwSave;
DependencyProperty IsBusySavingProperty = ...;

private MyViewModel() {
    bwSave = new BackgroundWorker();

    bwSave.DoWork += (sender, args) => {
        // do your lengthy save stuff here -- this happens in a separate thread
    }

    bwSave.RunWorkerCompleted += (sender, args) => {
        IsBusySaving = false;
        if (args.Error != null)  // if an exception occurred during DoWork,
            MessageBox.Show(args.Error.ToString());  // do your error handling here
    }
}

private void Save() {
    if (IsBusySaving) {
        throw new Exception("Save in progress -- this should be prevented by the UI");
    }
    IsBusySaving = true;
    bwSave.RunWorkerAsync();
}

Solution 2

You're using MVVM pattern, so your Save Button's Command is set to an instance of the RoutedCommand object which is added to the Window's CommandBindings collection either declaratively or imperatively.

Assuming that you do it declaratively. Something like

<Window.CommandBindings>
    <CommandBinding
        Command="{x:Static namespace:ClassName.StaticRoutedCommandObj}"
        CanExecute="Save_CanExecute"
        Executed="Save"
    />
</Window.CommandBindings>

For the handler of Executed routed event, your Save() method, on entry, you set a variable to false, on return you set it back to true. Something like.

void Save(object sender, ExecutedRoutedEventArgs e)
{
    _canExecute = false;
    // do work
    _canExecute = true; 
}

For the handler of the CanExecute routed event, the Save_CanExecute() method, you use the variable as one of the condition.

void ShowSelectedXray_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = _canExecute && _others;
}

I hope I am clear. :)

Share:
15,544

Related videos on Youtube

Thomas Stock
Author by

Thomas Stock

I'm a .NET developer that focuses on web technologies and UI. I love working with JavaScript and I believe that this language will play a major role in future software development. My weapons of choice are: ASP.NET MVC (+ Web API) jQuery / AngularJS Umbraco CMS Xamarin (iOS Development) I also have a strong interest in: UX design / Usability Rock Climbing I'm not afraid of css and I think twitter is overrated :-) Specialties: Front-end, JavaScript, .NET

Updated on May 01, 2022

Comments

  • Thomas Stock
    Thomas Stock about 2 years

    I'm using the MVVM pattern in my first WPF app and have a problem with something quite basic I assume.

    When the user hits the "save" button on my view, a command gets executed that calls the private void Save() in my ViewModel.

    The problem is that the code in "Save()" takes some time to execute, so I'd like to hide the "Save" button in the UI view before executing the large chunk of code.

    The problem is that the view doesn't update untill all code is executed in the viewmodel. How can I force the view to redraw and process the PropertyChanged events before executing the Save() code?

    Additionally, I would like a reuseable way, so that I can easily do the same thing in other pages as well.. Anyone else made something like this already? A "Loading..." message?

  • Thomas Stock
    Thomas Stock over 14 years
    sorry I'm a total dumbo when it comes to threading. Inside the Save code I (sometimes) try to navigate to another page. But because I'm in another thread, this gives a runtime error. I guess I have to do a callback to the original thread and navigate from there to the other page. But I'll try this myself, I'm sure it's not hard to communicated with the original thread.
  • Thomas Stock
    Thomas Stock over 14 years
    "The calling thread cannot access this object because a different thread owns it." is the message I get. If you know by heart what I need, let me know :-)
  • Can Sahin
    Can Sahin over 14 years
    (1) If you set _canSave instead of CanSave, OnChange will not be raised. (2) I don't think it will work, since Save runs in the UI thread, so the WPF UI won't be updated until Save has finished.
  • Thomas Stock
    Thomas Stock over 14 years
    yes indeed, I appriciate the answer but I don't think it fixes my problem. Heinzi's suggestion fixed it.
  • Thomas Stock
    Thomas Stock over 14 years
    RunWorkerCompleted, that's what I needed. Thanks a lot. Also awesome to see I can declare the eventhandler like that, didnt know that!
  • Can Sahin
    Can Sahin over 14 years
    Yes, RunWorkerCompleted is exactly the right place for that since it runs in the UI thread. If you need to change UI stuff during the Save operation, you can either use Application.Current.Dispatcher.Invoke or the ReportProgress method/ProgressChanged event combination of the BackgroundWorker.
  • tranmq
    tranmq over 14 years
    You still need to disable your save button when IsBusySaving == true, or you have to provide a lock in the DoWork in order to NOT getting yourself into a race condition.
  • Thomas Stock
    Thomas Stock over 14 years
    is it sufficient to set the CanExecute on "IsBusySaving == false" of my SaveCommand?
  • Can Sahin
    Can Sahin over 14 years
    I guess so. If you want to make something fancy, you can also add some "Saving in progress..." text on your UI whose visibility is bound to IsBusySaving via a BooleanToVisibilityConverter.
  • Pete OHanlon
    Pete OHanlon over 14 years
    @Heinzi - good spot on the CanSave, and yes it will work because the notification change is raised at the start of the save operation - hence, the UI updates at that point.
  • Thomas Stock
    Thomas Stock almost 14 years
    thanks for your answer. I used the accepted answer in my application and it works fine. I don't know how the resource usage is as compared to your solution but the backgroundworker is very convenient to work with.
  • Gusdor
    Gusdor almost 13 years
    Keith: In .Net 3.5 and above, consider lambda expressions to trim the state object if you don't need it. ThreadPool.QueueUserWorkItem(state => Save()); Perhaps a little more overhead here but the code is often made simpler with less delegate appeasing methods floating around. You can also use a lambda to cast the state object to whatever you need and pull out specific properties. ThreadPool.QueueUserWorkItem(state => Save(state as SaveArgs).Length, state as SaveArgs).TimeStamp);