Обновление пользовательского интерфейса из класса ViewModel (шаблон MVVM) в WPF - PullRequest
9 голосов
/ 18 ноября 2009

Я использую шаблон MVVM в своем первом приложении WPF, и у меня возникла проблема с чем-то довольно простым, я полагаю.

Когда пользователь нажимает кнопку «Сохранить» в моем представлении, запускается команда, которая вызывает приватный void Save () в моей ViewModel.

Проблема в том, что выполнение кода в «Save ()» занимает некоторое время, поэтому я хотел бы скрыть кнопку «Сохранить» в представлении пользовательского интерфейса перед выполнением большого фрагмента кода.

Проблема в том, что представление не обновляется, пока весь код не выполняется в модели представления. Как я могу заставить представление перерисовать и обработать события PropertyChanged перед выполнением кода Save ()?

Кроме того, я хотел бы использовать повторно, чтобы я мог легко сделать то же самое и на других страницах. Кто-нибудь уже сделал что-то подобное? Сообщение "Идет загрузка ..."?

Ответы [ 6 ]

11 голосов
/ 18 ноября 2009

Если это занимает много времени, рассмотрите возможность использования отдельного потока, например, с помощью BackgroundWorker, чтобы поток пользовательского интерфейса мог оставаться отзывчивым (т.е. обновлять пользовательский интерфейс) во время выполнения операции.

В вашем Save методе вы бы

  • изменение пользовательского интерфейса (то есть изменение некоторого логического значения INotifyPropertyChanged или DependencyProperty IsBusySaving, которое связано с вашим пользовательским интерфейсом, скрывает кнопку Сохранить и, возможно, отображает индикатор выполнения с IsIndeterminate = True) и
  • начать BackgroundWorker.

В обработчике событий DoWork вашего BackgroundWorker вы выполняете длительную операцию сохранения.

В обработчике событий RunWorkerCompleted, который выполняется в потоке пользовательского интерфейса, вы устанавливаете IsBusySaving в значение false и, возможно, изменяете другие элементы в пользовательском интерфейсе, чтобы показать, что вы закончили.

Пример кода (не проверено):

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 голосов
/ 18 ноября 2009

Вы используете шаблон MVVM, поэтому в качестве значения Command для кнопки «Сохранить» задан экземпляр объекта RoutedCommand, который добавляется в коллекцию CommandBindings окна либо декларативно, либо обязательно.

Предполагая, что вы делаете это декларативно. Что-то вроде

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

Для обработчика перенаправленного события Executed в вашем методе Save () при входе вы устанавливаете переменную в false, по возвращении вы возвращаете ее в значение true. Нечто подобное.

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

Для обработчика перенаправленного события CanExecute, метода Save_CanExecute (), вы используете переменную в качестве одного из условий.

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

Надеюсь, мне ясно. :)

0 голосов
/ 15 июля 2010

Поздний ответ, но я подумал, что было бы неплохо также немного ввести.

Вместо создания собственного нового потока, вероятно, было бы лучше оставить его в пуле потоков для запуска сохранения. Это не заставляет его запускаться мгновенно, как при создании собственного потока, но позволяет экономить ресурсы потоков.

Способ сделать это:

ThreadPool.QueueUserWorkItem(Save);

Проблема использования этого подхода также состоит в том, что вам необходимо, чтобы ваш метод "Save ()" воспринимал объект, который будет действовать как состояние. У меня была проблема, похожая на вашу, и я решил пойти по этому пути, потому что место, где я работаю, очень нуждается в ресурсах.

0 голосов
/ 19 ноября 2009

Вы можете сделать это с помощью следующего кода ..

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

Выполните весь ваш длительный процесс в dowork () method

другим способом ...

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

Это приведет к запуску процесса сохранения в другом потоке и не заблокирует ваш пользовательский интерфейс, если вы хотите показать анимацию, пока пользователь нажимает кнопку «Сохранить», а затем отображать некоторые индикаторы выполнения, такие как iPhone и т. Д. постараюсь помочь вам еще больше.

0 голосов
/ 19 ноября 2009
0 голосов
/ 18 ноября 2009

Вы всегда можете сделать что-то вроде этого:

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));
    }
  }
}

Затем вы можете привязать свойство IsEnabled кнопки к свойству CanSave, и оно будет автоматически включено / отключено. Альтернативный метод, и я хотел бы использовать для его сортировки команду CanExecute, но идея достаточно схожа для работы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...