#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:
Где-то уведомление не проходит.
Я бы попробовал :
- Добавьте фиктивный преобразователь значений в текстовую привязку, чтобы вы могли установить точку останова и посмотреть, вызван ли вы
- Отправка набора свойств для установки значения в «лучшее» время — иногда это необходимо.
Отправка набора может помочь.