Строка состояния не всегда обновляется

#wpf #multithreading #mvvm #c#-4.0

#wpf #многопоточность #mvvm #c #-4.0

Вопрос:

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

В моей ViewModel у меня есть базовое свойство, которое я обновляю при изменении сообщения о состоянии:

 public string StatusMessage
{
    get { return _statusMessage; }
    set
    {
        if (value == _statusMessage) return;
        _statusMessage = value;
        base.OnPropertyChanged(() => this.StatusMessage);
    }
}
  

Мой метод OnPropertyChanged (который у меня есть в базовом классе ViewModel, который реализует INotifyPropertyChanged) выглядит примерно так (получил эту идею от Гюнтера Фойдла; хотел бы я приписать это, потому что я думаю, что это гладко, но я не настолько умен):

 protected virtual void OnPropertyChanged<T>(Expression<Func<T>> exp)
    {
    MemberExpression me = exp.Body as MemberExpression;
    string propName = me.Member.Name;

    PropertyChangedEventHandler handler = this.PropertyChanged;
    if (handler != null)
    {
        handler(this, new PropertyChangedEventArgs(propName));
    }
}
  

В любом случае, все это отлично работает для всех моих элементов управления, кроме одного. В моем файле MainWindow.xaml у меня есть элемент управления StatusBarItem, привязанный к вышеуказанному свойству, примерно так (остальная часть XAML была урезана из соображений экономии места):

 <StatusBarItem Grid.Column="0">
    <TextBlock TextTrimming="CharacterEllipsis" Text="{Binding Path=StatusMessage}" />
</StatusBarItem>
  

Когда я запускаю свое приложение (которое обрабатывает пару баз данных в дополнение к созданию документа из шаблона и множеству других довольно ресурсоемких материалов), некоторые, но не все, сообщения отображаются в строке состояния. Я отладил и проверил, что все сообщения попадают в свойство StatusMessage выше (и последующую закрытую переменную), они просто, похоже, не обновляются в пользовательском интерфейсе.

Я просмотрел несколько примеров, в которых используются экземпляры BackgroundWorker для элементов управления ProgressBar, но не видел ни одного для элементов управления StatusBarItem, и я не совсем уверен, как перевести один в другой.

Я также использовал Tasks ранее в предыдущих приложениях C # 4.0 и WPF и полагаю, что это, вероятно, хороший способ, но я действительно не смог выяснить, как / где назначить задачу пользовательского интерфейса (я всегда делал это в коде для MainWindow раньше, но я стремлюсь к нулевому коду, чтобы оставаться в соответствии с MVVM здесь).

Я почти уверен, что многопоточный подход — это правильный путь; я просто недостаточно знаю об одном подходе (я знаю немного об этом и немного о том), чтобы заставить его работать. Я видел пару сообщений, в которых напрямую использовался старый подход к обработке потоков, но я в значительной степени избегал многопоточного программирования, пока не начал использовать задачи с .NET 4.0 (обнаружив, что их немного легче понять и отслеживать), поэтому у меня были небольшие проблемы с их пониманием.

Может ли кто-нибудь сжалиться надо мной и указать мне правильное направление или предложить дальнейшую отладку, которую я могу выполнить? Спасибо!

Комментарии:

1. У меня аналогичная проблема с обновлением текстового блока, вы нашли решение в конце концов?

2. Я смог заставить код задачи работать, передав мой пользовательский интерфейс TaskScheduler в мою ViewModel и выполнив обновление задачи, созданной из указанного планировщика. Я опубликую примеры кода в отдельном ответе, когда позволит время.

3. @Julien, вот что я сделал. При инициализации моей ViewModel в моем файле App.xaml.cs я передал 2 экземпляра TaskFactory: один для UI:, new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext()) который я вызвал UiTasks , и один для всего остального, Task.Factory что я вызвал DefaultTasks . Затем я убедился, что моя тяжелая работа выполняется на фабрике задач по умолчанию, и обновил свое свойство, используя фабрику пользовательского интерфейса: this.UiTasks.StartNew(() => this.StatusMessage = myMessage; }); Похоже, это помогло. Я буду рад выслать вам более подробный пример, если хотите. Удачи!

4. Спасибо за объяснение, я попробую это как можно скорее. Я перезвоню тебе, если у меня не получится 😉

5. Мне удалось заставить это работать, используя tastefactory! Спасибо за вашу помощь 😉

Ответ №1:

1) Привязка на основе отражения иногда может быть источником ошибки из-за встраивания. Попробуйте посмотреть, что произойдет, если вы notifypropertychanged с простой строкой вместо отражения.

2) если вы используете несколько потоков, возможно, есть вероятность, что вы настроите StatusMessage не из UIThread в этом случае он не сможет обновить пользовательский интерфейс, вы могли бы вызвать код настройки в UI Dispatcher, чтобы посмотреть, помогает ли это

3) проверьте, работает ли привязка, в конструкторе формы xaml измените StatusMessage непосредственно на виртуальной машине и посмотрите, отображается ли изменение в пользовательском интерфейсе без вызова многопоточных вызовов служб, которые вводят дополнительные переменные в простую привязку textblock — string

4) если это не помогает, вы могли бы создать простую форму xaml с одним текстовым блоком, привязать ее к вашей большой viewmodel и посмотреть, что произойдет, если ничего не работает, вы можете начать сокращать класс виртуальной машины, чтобы упростить его, чтобы привязка в конечном итоге начала работать, и вы обнаружили ошибку

5) если вы считаете, что проблема в строке состояния, посмотрите, работает ли отдельный текстовый блок без строки состояния (извлеките часть xaml из вашего примера)

Комментарии:

1. Спасибо, Валентин. Как оказалось, проблема заключалась в том, что сообщение о состоянии не обновлялось из потока пользовательского интерфейса (и в то время я не мог понять, как это сделать с помощью MVVM). Как только я смог настроить пользовательский интерфейс TaskScheduler / TaskFactory в моей программе App_Startup и передать его в конструктор моей ViewModel, а затем использовать его для создания задачи для обновления статуса, это сработало как по волшебству. Спасибо!

2. Я не понимаю, почему исходный поток является проблемой. WPF предназначен для автоматической отправки уведомлений о привязках к потоку пользовательского интерфейса, не так ли?

Ответ №2:

Где-то уведомление не проходит.

Я бы попробовал :

  • Добавьте фиктивный преобразователь значений в текстовую привязку, чтобы вы могли установить точку останова и посмотреть, вызван ли вы
  • Отправка набора свойств для установки значения в «лучшее» время — иногда это необходимо.

Отправка набора может помочь.