9
votes

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?

6

6 Answers

11
votes

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();
}
3
votes

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

0
votes

You could always do something like this:

public class SaveDemo : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;
  private bool _canSave;

  public bool CanSave
  {
    get { return _canSave; }
    set
    {
      if (_canSave != value)
      {
        _canSave = value;
        OnChange("CanSave");
      }
    }
  }

  public void Save()
  {
    _canSave = false;

    // Do the lengthy operation
    _canSave = true;
  }

  private void OnChange(string p)
  {
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
    {
      handler(this, new PropertyChangedEventArgs(p));
    }
  }
}

Then you could bind the IsEnabled property of the button to the CanSave property, and it will automatically be enabled/disabled. An alternative method, and one I would go with would be to use the Command CanExecute to sort this, but the idea is similar enough for you to work with.

0
votes

You can accomplish this by the following code..

Thread workerThread = null;
void Save(object sender, ExecutedRoutedEventArgs e)
{
workerThread = new Thread(new ThreadStart(doWork));
SaveButton.isEnable = false;
workerThread.start();
}

do all your lengthy process in dowork() method

in some other method...

workerThread.join();
SaveButtton.isEnable = true;

This will cause to run save lengthy process in another thread and will not block your UI, if you want to show an animation while user click on save button then show some progress bar like iPhone etc... give me feedback i'll try to help you even more.

0
votes

Late answer, but I figured it'd be good to input a bit as well.

Instead of creating your own new thread, it would probably be better to leave it up to the threadpool to run the save. It doesn't force it to run instantly like creating your own thread, but it does allow you to save threading resources.

The way to do that is:

ThreadPool.QueueUserWorkItem(Save);

The problem with using this approach, as well, is that you're required to have your "Save()" method take in an object that will act as a state. I was having a similar problem to yours and decided to go this route because the place that I'm working is very Resource-Needy.