如何在可观察集合的 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 中立即反映任何添加、删除、字段更改的主要内容,如果未能解决你的问题,请参考以下文章