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 个类的主要内容,如果未能解决你的问题,请参考以下文章

插件式框架探索系列使用多UI线程提升性能

WPF线程调用UI元素

在WPF中减少逻辑与UI元素的耦合

WPF:如果 UI 元素不可见,则停止绑定

WPF到跨平台开发,最强大的跨平台UI框架是啥?

实体框架 + SQL Server Compact + WPF/WinForms = 缓慢的 UI?