Убедитесь, что OnPropertyChanged () вызывается в потоке пользовательского интерфейса в приложении MVFM WPF - PullRequest
35 голосов
/ 26 февраля 2009

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

Я использую шаблон MVVM, поэтому моя ViewModel практически ничего не знает о представлении (пользовательском интерфейсе) модели, которое представляет модель пользователю.

Скажем, у меня есть следующий метод в моей ViewModel:

public void backgroundWorker_ReportProgress(object sender, ReportProgressArgs e)
{
    this.Messages.Add(e.Message);
    OnPropertyChanged("Messages");
}

На мой взгляд, у меня есть ListBox, связанный со свойством Messages (a List<string>) ViewModel. OnPropertyChanged выполняет роль интерфейса INotifyPropertyChanged, вызывая PropertyChangedEventHandler.

Мне нужно убедиться, что OnPropertyChanged вызывается в потоке пользовательского интерфейса - как мне это сделать? Я пробовал следующее:

public Dispatcher Dispatcher { get; set; }
public MyViewModel()
{ 
    this.Dispatcher = Dispatcher.CurrentDispatcher;
}

Затем добавьте следующее к методу OnPropertyChanged:

if (this.Dispatcher != Dispatcher.CurrentDispatcher)
{
    this.Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(delegate
    {
        OnPropertyChanged(propertyName);
    }));
    return;
}

но это не сработало. Есть идеи?

Ответы [ 5 ]

34 голосов
/ 26 февраля 2009

WPF автоматически изменяет свойства маршала в потоке пользовательского интерфейса. Однако это не приводит к изменению коллекции, поэтому я подозреваю, что добавление сообщения вызывает сбой.

Вы можете самостоятельно выполнить добавление вручную (см. Пример ниже) или использовать что-то вроде этой техники Я недавно писал в блоге.

Ручная сортировка:

public void backgroundWorker_ReportProgress(object sender, ReportProgressArgs e)
{
    Dispatcher.Invoke(new Action<string>(AddMessage), e.Message);
    OnPropertyChanged("Messages");
}

private void AddMessage(string message)
{
    Dispatcher.VerifyAccess();
    Messages.Add(message);
}
8 голосов
/ 05 февраля 2010

Мне ДЕЙСТВИТЕЛЬНО нравится ответ Джереми: Отправка в Silverlight

Резюме:

  • Размещение диспетчера во ViewModel кажется не элегантным

  • Создавая свойство Action , установите его для запуска действия в конструкторе VM

  • При использовании виртуальной машины из V установите свойство Action для вызова Dispatcher
3 голосов
/ 26 апреля 2009

У меня был похожий сценарий только на этой неделе (MVVM здесь также). У меня был отдельный класс, который делал свое дело, сообщая о состоянии обработчика событий. Обработчик события вызывался, как и ожидалось, и я мог видеть, что результаты возвращаются точно в срок с помощью Debug.WriteLine .

Но с WPF, что бы я ни делал, пользовательский интерфейс не обновлялся до завершения процесса. Как только процесс завершится, пользовательский интерфейс будет обновлен, как и ожидалось. Это было так, как будто он получал PropertyChanged, но ожидал завершения потока, прежде чем выполнять обновления интерфейса пользователя сразу.

(К моему ужасу, тот же код в Windows. Формы с DoEvents и .Refresh () работали как чудо.)

Пока что я решил эту проблему, запустив процесс в своем собственном потоке:

//hook up event handler
myProcess.MyEvent += new EventHandler<MyEventArgs>(MyEventHandler); 

//start it on a thread ...
ThreadStart threadStart = new ThreadStart(myProcess.Start);

Thread thread = new Thread(threadStart);

thread.Start();

и затем в обработчике событий:

private void MyEventHandler(object sender, MyEventArgs e) { 
....
Application.Current.Dispatcher.Invoke(
                DispatcherPriority.Send,
                (DispatcherOperationCallback)(arg =>
                { 
         //do UI updating here ...
        }), null);

Я не рекомендую этот код, так как я все еще пытаюсь понять модель потока WPF, как работает Dispatcher и почему в моем случае пользовательский интерфейс не будет обновляться до тех пор, пока процесс не будет завершен даже после вызова обработчика события. как и ожидалось (по замыслу?). Но до сих пор это работало для меня.

Мне показались полезными эти две ссылки:

http://www.nbdtech.com/blog/archive/2007/08/01/Passing-Wpf-Objects-Between-Threads-With-Source-Code.aspx

http://srtsolutions.com/blogs/mikewoelmer/archive/2009/04/17/dealing-with-unhandled-exceptions-in-wpf.aspx

0 голосов
/ 26 мая 2011

Это скорее расширение принятого ответа, но я сделал это с помощью своего обработчика событий ...

using System.Threading;

private void Handler(object sender, RoutedEventArgs e)
{
    if (Thread.CurrentThread == this.Dispatcher.Thread)
    {
        //do stuff to this
    }
    else
    {
        this.Dispatcher.Invoke(
            new Action<object, RoutedEventArgs>(Handler),
            sender,
            e);
    }
}
0 голосов
/ 15 мая 2009

Я обрабатываю событие BackgroundWorker.ReportProgress вне моего ViewModel и передаю фактический экземпляр BackgroundWorker и ViewModel в мой класс, который определяет метод (ы) async.

Затем метод async вызывает bgWorker.ReportProgress и передает класс, который оборачивает делегата как UserState (как object). Делегат, который я пишу как анонимный метод.

В обработчике событий я преобразую его из object обратно в тип оболочки, а затем вызываю делегата внутри.

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

Это объясняет это более подробно:

http://lukepuplett.blogspot.com/2009/05/updating-ui-from-asynchronous-ops.html

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