Подтвердить что ты не робот

Убедитесь, что OnPropertyChanged() вызывается в потоке пользовательского интерфейса в приложении MVVM WPF

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

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

Скажем, у меня есть следующий метод в моей модели 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 вызывается в потоке UI - как это сделать? Я пробовал следующее:

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

но это не сработало. Любые идеи?

4b9b3361

Ответ 1

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

Ответ 2

Я ДЕЙСТВИТЕЛЬНО люблю Джереми: Отправка в Silverlight

Резюме:

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

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

  • При использовании виртуальной машины из V установите свойство Action для вызова Диспетчера

Ответ 3

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

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

(К большому сожалению, тот же код в Windows.Forms с 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, как работает диспетчер, и почему в моем случае пользовательский интерфейс не будет обновляться до тех пор, пока процесс не будет завершен даже при вызове обработчика события как ожидалось (по дизайну?). Но это сработало для меня до сих пор.

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

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

Ответ 4

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

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

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

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

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

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

Ответ 5

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

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