此更新 UI 通过 dispatcher/databinding 缺少什么

What is missing in this update UI via dispatcher/databinding

我有一个简单的 WPF window:Loaded="StartTest"

<Grid>
        <ListBox ItemsSource="{Binding Logging, IsAsync=True}"></ListBox>
</Grid>

在方法 StartTest:

后面的代码中
LogModel LogModel = new LogModel();

void StartTest(object sender, RoutedEventArgs e)
{
    DataContext = LogModel;

    for (int i = 1; i<= 10; i++)
    {
       LogModel.Add("Test");
       Thread.Sleep(100);
    }
}

而classLogModel是:

public class LogModel : INotifyPropertyChanged
{
    public LogModel()
    {
        Dispatcher = Dispatcher.CurrentDispatcher;
        Logging = new ObservableCollection<string>();
    }
    Dispatcher Dispatcher;

    public ObservableCollection<string> Logging { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    public void Add(string text)
    {
        Dispatcher.BeginInvoke((Action)delegate ()
        {
            Logging.Add(text);
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Logging"));
        });
    }
}

当然问题是 UI 没有在循环中更新。 我错过了什么?
如何实现 UI 更新?

循环中UI没有更新的原因是对Dispatcher.BeginInvoke的调用。这会在调度程序队列中放置一个新的 DispatcherOperation。但是你的循环已经是一个调度程序操作,它在 Dispatcher 的线程上继续。所以你排队的所有操作都会在循环操作完成后执行。

也许您想在后台线程上 运行 StartTest?然后,UI 将更新。

顺便说一下,不要用Thread.Sleep阻塞Dispatcher的线程。它会阻止 Dispatcher 尽可能顺利地完成任务。

ObservableCollection 已在修改时引发 PropertyChanged 事件。您也不必在 UI 线程中引发事件。

您的模型可以简单到:

class LogModel
{
    public ObservableCollection<string> Logging { get; } = new ObservableCollection<string>();

    public void Add(string text)
    {
        Logging.Add(text);
    }
}

您只需将其设置为您的视图 DataContext,例如:

LogModel model = new LogModel();
public MainWindow()
{
    InitializeComponent();
    this.DataContext = model;
}

我假设 StartTest 是一个单击处理程序,这意味着它在 UI 线程上运行。这意味着它将 阻塞 UI 线程,直到循环结束。一旦循环完成,UI 将被更新。

如果您希望 UI 在循环期间保持响应,请使用 Task.Delay 而不是 Thread.Slepp,例如:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    for(int i=0;i<10;i++)
    {
        await Task.Delay(100);
        model.Add("Blah!");
    }
}

更新

您不需要使用 ObservableCollection 作为数据绑定源。您可以使用任何对象,包括数组或列表。在这种情况下,尽管您必须在代码中引发 PropertyChanged 事件:

class LogModel:INotifyPropertyChanged
{
    public List<string> Logging { get; } = new List<string>();

    public event PropertyChangedEventHandler PropertyChanged;

    public void Add(string text)
    {
        Logging.Add(text);
        PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Logging"));
    }
}

这将告诉视图加载所有 内容并再次显示它们。当您只想显示从数据库加载的数据而不修改它们时,这非常好,因为它使映射实体到 ViewModel 变得容易得多。在这种情况下,您只需在通过命令附加新的 ViewModel 时更新视图。

当您需要更新冷却器时,这不是有效的。 ObservableCollection 实现 INotifyCollectionChanged 接口,该接口为每个更改引发一个事件。如果您添加一个新项目,将只呈现该项目。

另一方面您应该避免在紧密循环中修改集合,因为它会引发多个事件。如果加载 50 个新项目,请不要循环调用 Add 50 次。创建一个新的 ObservableCollection,替换旧的并引发 PropertyChanged 事件,例如:

class LogModel:INotifyPropertyChanged
{
    public ObservableCollection<string> Logging { get; set; } = new ObservableCollection<string>();

    public event PropertyChangedEventHandler PropertyChanged;

    public void Add(string text)
    {            
        Logging.Add(text);
        PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Logging"));
    }

    public void BulkLoad(string[] texts)
    {
        Logging = new ObservableCollection<string>(texts);
        PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Logging"));
    }
}

仍然需要显式实现,因为 Logging 属性 正在被替换并且本身不能引发任何事件

DoEvents的东西,overhere:

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                                          new Action(delegate { }));
}

甚至可能更好。

当然应该以不需要它的方式编写不同的测试。