如何在可观察集合的 UI 中立即反映任何添加、删除、字段更改

Posted

技术标签:

【中文标题】如何在可观察集合的 UI 中立即反映任何添加、删除、字段更改【英文标题】:How to reflect any Adds, Removes, field changes immediately in the UI for Observable Collection 【发布时间】:2021-09-30 22:13:45 【问题描述】:

我在将 Observable 集合的更改(添加、删除、字段更改)立即显示在我的 TableView UI 中时遇到了真正的问题。

我写了一个小程序来测试功能。它可以工作,但不会立即反映更改,它会等到所有添加、删除、更改都完成后才会显示结果。

我希望它显示每个发生的变化。即,如果我添加一条记录,它会立即显示更改。 UI 应向下滚动屏幕,显示添加内容。

在我的代码中,我在添加、删除和更改之间添加了休眠以证明这一点。

我需要做什么才能使所有更改(添加、删除、字段更改)立即得到反映?

附上我的一些代码。

namespace ObservableTest

  internal static class Common
  
    public static ObservableCollection OC = new ObservableCollection();

    public static void AddRecords(int HowMany = 100)
    
        for(int i = 0; i < HowMany; i++)
        
            OC.Add(new TestObservable("StringData " + i.ToString(), i));
            Thread.Sleep(1000); // Sleep to see outcome in UI
        
    

    public static void RemoveRecords(int HowMany = 5)
    
        var data = OC.ToList();
        foreach(var field in data)
        
            if(field.IntField < HowMany)
            
                OC.Remove(field);
                Thread.Sleep(1000); // Sleep to see outcome in UI
            
        
    

    public static void ChangeRecords(int HowMany = 5)
    
        var data = OC.ToList();
        foreach(var field in data)
        
            field.StringField += " C";
            Thread.Sleep(1000); // Sleep to see outcome in UI
        
    

public class TestObservable : INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;
    public void NotifyPropertyChanged(string propName)
     this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); 

    private string stringField = "";
    private int intField = 0;

    public TestObservable(string StringField, int IntField)
    
        this.stringField = StringField;
        this.intField = IntField;
    
    public string StringField
    
        get  return stringField; 
        set
        
            if (this.stringField != value)
            
                this.stringField = value;
                this.NotifyPropertyChanged("StringField");
            
        
    
    
    public int IntField
    
        get  return intField; 
        set
        
            if (this.intField != value)
            
                this.intField = value;
                this.NotifyPropertyChanged("IntField");
            
        
     
  

【问题讨论】:

您用于视图的视图模型是什么? xaml 代码是什么样的?你的问题不清楚 睡眠如何让 UI 更新?这是在后台线程上完成的吗? @EldHasp 您的回答将集合修改移动到调度程序,同时您继续阻塞 UI 线程。但是您应该摆脱任何 UI 阻塞代码(如果它长时间运行)。这就是 .NET 提供任务库的原因。这就是 C# 提供 async/await 的原因。这个概念(你也可以在其他语言中找到)允许我们编写可以在 UII 线程上执行而不阻塞它的代码。 您不想阻塞 UI 线程,因为它会立即对 UI 产生影响,这将停止渲染,从而导致“冻结”UI,其中所有顺序更新都会同时发生一旦线程再次有空闲资源。 @EldHasp 您的回答还有很多问题。 【参考方案1】:

您应该异步执行阻塞代码。

要将您的示例转换为异步,只需将阻塞 Thread.Sleep 替换为非阻塞 Task.Delay

关键是 Thread.Sleep 确实阻塞了 UI 线程。当 UI 线程处于休眠状态时,它无法执行任何其他操作,例如呈现更改。 除了 Thread.Sleep:当你的阻塞(同步)代码异步执行时,你允许 UI 线程保持响应。 UI 线程将能够在执行异步代码时呈现视图中的更改。

internal static class Common

  public static ObservableCollection<TestObservable> OC = new ObservableCollection<TestObservable>();

  public static async Task AddRecords(int HowMany = 100)
  
    for (int i = 0; i < HowMany; i++)
    
      OC.Add(new TestObservable("StringData " + i.ToString(), i));
      await Task.Delay(1000); // Sleep to see outcome in UI
    
  

  public static async Task RemoveRecords(int HowMany = 5)
  
    var data = OC.ToList();
    foreach (var field in data)
    
      if (field.IntField < HowMany)
      
        OC.Remove(field);
        await Task.Delay(1000); // Sleep to see outcome in UI
      
    
  

  public static async Task ChangeRecords(int HowMany = 5)
  
    var data = OC.ToList();
    foreach (var field in data)
    
      field.StringField += " C";
      await Task.Delay(1000); // Sleep to see outcome in UI
    
  

【讨论】:

在问题中,Sleep 表示在同步代码中执行的大量不同操作。用等待来模拟它们的执行,使用Delay 的结果是不够的。仔细阅读问题的本质。 我说你应该把你的同步代码转换成异步代码。 Task.Delay 正是这样做的。 Task.Delay “模拟”异步执行的操作。如果您希望 UI 保持响应(即及时更新),重点是不要阻止 UI。 我完全理解睡眠和延迟之间的区别。但 TC 并没有就此提出任何问题。事实上,你已经为自己发明了一个不同的问题并回答了你的这个问题。 “我编写了一个小程序来测试功能。它可以工作,但不会立即反映更改,它会等到所有添加、删除、更改都完成后才会显示结果。” 你读过吗?他说 “它可以工作,但不会立即反映更改,它会等到所有添加、删除、更改都完成后才会显示结果。” - 这是由于 UI 线程被阻塞。我想我已经非常正确地理解了这个问题。 程序检查什么功能?如何正确延迟?或者如何正确执行需要大量时间的大量命令?在实际代码中,睡眠和延迟都是没有意义的。他们只需要被删除。 Sleep 仅在使用 TO EMULATE 长期同步操作的情况下才有意义。什么可以帮助测试和/或模拟Delay,我没有想到。【参考方案2】:

您需要使用异步方法。 所有长时间运行的工作都应该在线程池上执行的任务中进行。 并且只有 WPF 视图中集合绑定的直接更改应该发生在应用程序的主线程上。 要在主线程上执行操作,首先需要获取它,然后传递给它的方法委托将其放入执行队列。 放入队列后,您可以等待它们的执行完成(如我的示例),但您也可以跳过等待并立即继续进一步处理。 这取决于您正在实施的算法以及处理结果的需要。

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;

namespace ObservableTest

    public static class Common
    
        public static ObservableCollection<TestObservable> OC  get; 
            = new ObservableCollection<TestObservable>();

        public static Dispatcher Dispatcher  get;  = Application.Current.Dispatcher;

        private static readonly Action<TestObservable> AddRecord = OC.Add;
        private static readonly Func<TestObservable, bool> RemoveRecord = OC.Remove;
        private static readonly Action<int, TestObservable> ReplaceRecord = (index, item) => OC[index] = item;

        // Synchronous method. It is undesirable to call it from the UI thread.
        public static void AddRecords(int howMany = 100)
        
            for (int i = 0; i < howMany; i++)
            
                TestObservable test = new TestObservable("StringData " + i.ToString(), i);
                DispatcherOperation operation = Dispatcher.BeginInvoke(AddRecord, test);
                DispatcherOperationStatus status = operation.Wait();
                Thread.Sleep(1000); // Sleep to see outcome in UI
            
        

        // Asynchronous method. It can be called from the UI thread.
        public static async void AddRecordsAsync(int howMany = 100)
            => await Task.Run(() => AddRecords(howMany));

        public static void RemoveRecords(int howMany = 5)
        
            var data = OC.ToList();
            foreach (TestObservable field in data)
            
                if (field.IntField < howMany)
                
                    DispatcherOperation operation = Dispatcher.BeginInvoke(RemoveRecord, field);
                    DispatcherOperationStatus status = operation.Wait();
                    Thread.Sleep(1000); // Sleep to see outcome in UI
                
            
        
        public static async void RemoveRecordsAsync(int howMany = 5)
            => await Task.Run(() => RemoveRecords(howMany));

        public static void ChangeRecords(int howMany = 5)
        
            var data = OC.ToList();
            foreach (TestObservable field in data)
            
                field.StringField += " C";
                Thread.Sleep(1000); // Sleep to see outcome in UI
            
        
        public static async void ChangeRecordsAsync(int howMany = 5)
            => await Task.Run(() => ChangeRecords(howMany));
    

【讨论】:

以上是关于如何在可观察集合的 UI 中立即反映任何添加、删除、字段更改的主要内容,如果未能解决你的问题,请参考以下文章

如何在datagridview中插入、更新、删除?

如何在可观察线程结束之前停止应用程序?

当 MVVM 中的属性更改时通知可观察集合

如何在可观察的地图中抛出错误(rxjs 6,ng6)

在可编辑网格中,如何使 Ext 组合框在选择项目时立即完成编辑模式?

可观察值是另一个可观察值的一部分