#c# #wpf #data-binding #observablecollection
Вопрос:
Я создал пользовательский элемент FrameworkElement ( Battery.cs ) для представления данных пользователю в пользовательском интерфейсе. В классе Battery.cs у меня было несколько свойств зависимости, поэтому пользовательский интерфейс мог отслеживать различные изменения и повторно отображать объект при изменениях.
Я разместил эту наблюдаемую коллекцию в своем файле MainWindowViewModel.cs, который был привязан к основному виду через список.
Все работало должным образом, однако это было только для тестирования, так как мне нужно было переместить коллекцию в другой класс, который собирался управлять / обновлять батареи. Это управление должно было происходить асинхронно, и поэтому у меня возникло много проблем с вызовами DependencyProperties в классе Battery.cs, поскольку они находились в потоке пользовательского интерфейса, а не в потоке управления/процесса.
Поэтому я удалил свойства зависимостей и попытался переместить свойства зависимостей в файл MainWindowViewModel.cs. Теперь я не получаю ошибок о том, какой поток владеет собственностью, и я вижу, что батареи в коллекции ObservableCollection обновляются. Однако метод OnRender никогда не вызывается пользовательским интерфейсом. Таким образом, батареи больше никогда не отображаются/не отображаются.
Вот код для свойства DependencyProperty в файле MainWindowViewModel.cs
public static readonly DependencyProperty batteriesProperty = DependencyProperty.Register(
"Batteries",
typeof(ObservableCollection<Battery>),
typeof(MainWindow),
new UIPropertyMetadata(new ObservableCollection<Battery>()));
public ObservableCollection<Battery> Batteries
{
get { return tbModel.Modules[0].batteries; }
}
Я думаю, что моя главная проблема может заключаться в этой линии
new UIPropertyMetadata(new ObservableCollection<Battery>()));
Однако я, похоже, не могу понять, что это должно быть, или как настроить код таким образом, чтобы пользовательский интерфейс обновлял графику после того, как я вызвал InvalidateVisual в классе Battery.cs.
public void UpdatePacket(Packet packet)
{
packet= packet;
Voltage = packet.Voltage;
InvalidateVisual();
}
Метод InvalidateVisual() выполняется, однако переопределение OnRender никогда не выполняется.
Комментарии:
1. Прости, возможно, я неправильно понял твое объяснение. Вы пишете, что batteriesProperty является участником ViewModel? И, соответственно, вы передаете ViewModel свойству DataContext элемента FrameworkElement? Если это так, то модель представления не является частью Визуального дерева. Поэтому вызов InvalidateVisual () в ViewModel никоим образом не влияет на визуальное дерево и, соответственно, метод OnRender () вызываться не будет.
2. В классе FrameworkElement не вызывается функция InvalidateVisual (). Показанное свойство зависимости было записано в ViewModel, чтобы у нас не было конфликтов потоков. Когда у меня было свойство зависимости в классе Battery.cs и коллекция ObservableCollection в ViewModel, все работало нормально. Однако, как только я начал нарезать потоки и переместить коллекцию ObservableCollection туда, где она мне действительно была нужна в модели, у меня начались проблемы.
Ответ №1:
- Создание ViewModel, производного от DependecyObject, бессмысленно. Это только усложняет и запутывает реализацию.
- Свойство батареи типа ObservableCollection должно быть обычным свойством CLR, доступным только для чтения в модели представления.
public ObservableCollection<Battery>() Batteries {get;}
= new ObservableCollection<Battery>();
- Если экземпляр ViewModel существует во всем сеансе приложения, то в конструкторе ViewModel синхронизируйте привязки к этой коллекции.
protected static readonly Dispatcher Dispatcher = Application.Current.Dispatcher;
public MainWindowViewModel()
{
if (Dispatcher.CheckAccess())
{
BindingOperations.EnableCollectionSynchronization(Batteries, ((ICollection)Batteries).SyncRoot);
}
else
{
Dispatcher.Invoke(()=>BindingOperations.EnableCollectionSynchronization(Batteries, ((ICollection)Batteries).SyncRoot));
}
// Some Code
}
- Если экземпляры ViewModel могут быть уничтожены, заменяя друг друга, то при синхронизации привязок сохраняется ссылка на экземпляр ViewModel, и, следовательно, этот экземпляр не будет удален GC. Кроме того, синхронизация привязок не всегда обеспечивает потокобезопасность при работе с коллекцией.
В этих случаях вы не используете операции привязки.Включить синхронизацию коллекций ().
Но вместо этого вы всегда работаете с коллекцией только в потоке диспетчера.
protected static readonly Dispatcher Dispatcher = Application.Current.Dispatcher;
public MainWindowViewModel()
{
// Some Code
}
... SomeMethod(...)
{
// Some Code
if (Dispatcher.CheckAccess())
{
Batteries.Add(...);
}
else
{
Dispatcher.Invoke(()=>Batteries.Add(...));
// Or
Dispatcher.InvokeAsync(()=>Batteries.Add(...));
}
// Some Code
}
Комментарии:
1. Что ж, это помогло в том, что теперь я понимаю, почему мой рендеринг не происходил. Однако у меня все еще есть вопросы о том, какой поток владеет объектом. Я получаю
System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it.
2. Предоставленных вами сведений недостаточно для определения причины проблемы. Попробуйте создать минимальный проект, демонстрирующий эту проблему. И предоставьте его (через GitHub) для исследований.
3. Вам не нужно определять, какой поток выполняет действие. Вам нужно только определить, является ли это потоком диспетчера или нет. Чтобы проверить, используйте этот
Dispatcher.CheckAccess()
метод. Если он возвращает значение true, выполните действия в текущем потоке. Если значение false, то выполните маршалирование действий в потоке диспетчера.