c# - 通过调度程序/数据绑定(bind)更新 UI 中缺少什么

标签 c# wpf user-interface dispatcher

我有一个简单的 WPF 窗口: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);
    }
}

LogModel 是:

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更新?

最佳答案

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

您的模型可以很简单:

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

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

您需要做的就是将其设置为 View 的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 作为数据绑定(bind)源。您可以使用任何对象,包括数组或列表。在这种情况下,您必须在代码中引发 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"));
    }
}

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

当您需要更新 Coolection 时,这效率不高。 ObservableCollection 实现 INotifyCollectionChanged 接口(interface),该接口(interface)为每次更改引发一个事件。如果添加新项目,则只会渲染该项目。

另一方面您应该避免在紧密循环中修改集合,因为它会引发多个事件。如果您加载 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 属性正在被替换并且本身无法引发任何事件

关于c# - 通过调度程序/数据绑定(bind)更新 UI 中缺少什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50524126/

相关文章:

c# - 列出存储在 AppDomain 中的所有自定义数据

wpf - 具有相同 ViewModel 的多个 View

wpf - MVVM 模型责任

java - 无法让我的组合框和按钮工作。实现带有按钮的框时出现问题

C# 为什么不能进行委托(delegate)覆盖?

c# - TPL 数据流 BufferBlock<> 消费者在 WPF 的主线程中工作

c# - 是否可以在 Windows 10 上调整 BLE 连接间隔?

c# - 在标签中显示部分程序的运行时间

user-interface - 无法将 GUI 变量保持为全局变量

java - 如何将用户输入添加到ArrayList?