UI 框架元素的 WPF ObservableCollection<T> 嵌套了 2 个类
Posted
技术标签:
【中文标题】UI 框架元素的 WPF ObservableCollection<T> 嵌套了 2 个类【英文标题】:WPF ObservableCollection<T> of UI Framework Element Nested 2 Classes deep 【发布时间】:2021-11-13 07:53:14 【问题描述】:我创建了一个自定义 FrameWorkElement (Battery.cs) 以在 UI 中向用户表示数据。在 Battery.cs 类中,我有几个 dependencyProperties,因此 UI 可以监控各种更改并在更改时重新渲染对象。
我将 ObservableCollection 放在我的 MainWindowViewModel.cs 中,它通过 ListBox 绑定到主视图。
一切正常,但这仅用于测试,因为我需要将集合移至另一个将管理/更新电池的类。这种管理将异步发生,因此我在 Battery.cs 类中的 DependencyProperties 调用遇到了很多问题,因为它们是在 UI 线程上而不是在管理/进程线程上。
所以我删除了 DependencyProperties,并尝试将 DependencyProperty 向上移动到 MainWindowViewModel.cs。现在我没有收到关于哪个线程拥有所有权的错误,我可以看到 ObservableCollection 中的电池正在更新。但是,UI 永远不会调用 OnRender 方法。所以电池不再被渲染/显示。
这是 MainWindowViewModel.cs 中 DependencyProperty 的代码
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>()));
但是我似乎无法弄清楚它应该是什么,或者如何调整代码,以便一旦我在 Battery.cs 类中调用 InvalidateVisual 后 UI 会更新图形。
public void UpdatePacket(Packet packet)
packet= packet;
Voltage = packet.Voltage;
InvalidateVisual();
InvalidateVisual() 方法正在执行,但 OnRender 覆盖从未被执行。
【问题讨论】:
对不起,我可能误解了你的解释。您是在写 BatteryProperty 是 ViewModel 的成员吗?因此,您将 ViewModel 传递给 FrameWorkElement 的 DataContext 属性?如果是这样,那么 ViewModel 不是可视树的一部分。因此,在 ViewModel 中调用 InvalidateVisual() 不会对可视化树产生任何影响,因此不会调用 OnRender() 方法。 没有在 FrameworkElement 类上调用 InvalidateVisual()。显示的依赖属性是写在 ViewModel 中的,所以我们没有线程冲突。当我在 Battery.cs 类中拥有依赖属性并在 ViewModel 中拥有 ObservableCollection 时,一切正常。然而,一旦我开始线程化并将 ObservableCollection 移动到我在模型中实际需要它的位置,就是我开始遇到问题的时候了。 【参考方案1】:从 DependecyObject 派生 ViewModel 是没有意义的。这只会使实现变得复杂和混乱。
ObservableCollection 类型的 Batteries 属性必须是 ViewModel 中的常规 CLR 只读属性。
public ObservableCollection<Battery>() Batteries get;
= new ObservableCollection<Battery>();
-
如果 ViewModel 的实例存在于 Application 的整个会话中,则在 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 删除。此外,绑定的同步并不总是为使用集合提供线程安全。
在这些情况下,您没有使用 BindingOperations.EnableCollectionSynchronization ()。
但是,您始终只在 Dispatcher 线程中处理集合。
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
【讨论】:
嗯,这确实有帮助,现在我可以看到为什么我的渲染没有发生。但是我仍然有哪个线程拥有对象所有权的问题。我收到System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it.
您提供的详细信息不足以确定问题的原因。尝试制作一个演示此问题的最小项目。并提供(通过 GitHub)用于研究。
您不需要确定哪个线程正在执行该操作。您只需要确定它是否是Dispatcher线程。要检查,请使用Dispatcher.CheckAccess()
方法。如果返回 true,则在当前线程上执行操作。如果为 false,则将操作编组到 Dispatcher 线程。以上是关于UI 框架元素的 WPF ObservableCollection<T> 嵌套了 2 个类的主要内容,如果未能解决你的问题,请参考以下文章