Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update UI from ViewModel class (MVVM pattern) in WPF

Tags:

mvvm

wpf

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?

like image 776
Thomas Stock Avatar asked Nov 18 '09 09:11

Thomas Stock


2 Answers

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();
}
like image 161
Heinzi Avatar answered Oct 12 '22 18:10

Heinzi


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. :)

like image 24
tranmq Avatar answered Oct 12 '22 19:10

tranmq