【问题标题】:What is missing in this update UI via dispatcher/databinding通过调度程序/数据绑定在此更新 UI 中缺少什么
【发布时间】:2018-11-04 13:17:30
【问题描述】:

我有一个简单的 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 更新?

【问题讨论】:

  • 在这种情况下设置 IsAsync=True 是没有用的。
  • 是的,我添加它是为了避免它作为建议的解决方案。
  • 我实际上是想发布这个答案。抱歉:stackoverflow.com/a/20148603/7167572
  • 在引入async/await 和异步数据绑定之前 使用了调度程序。您不需要无论如何都需要在 UI 线程上引发事件,View 的 工作是根据数据更新自身。由于您使用ObservableCollection,您根本不需要引发事件,它是由集合本身引发的。 StartTest 阻塞 UI 线程本身。反正奇怪的东西太多了
  • @Gerard 看起来你在某个时候遇到了障碍并开始抛出代码来“修复”它?你的初衷是什么?使用日志消息定期更新 UI?您可以使用只有一个 Logging 属性的简单模型。只要StartTest 没有阻塞,即没有Thread.Sleep,你就不应该有任何问题。请改用await Task.Delay()。顺便说一句,您是否将视图的上下文设置为模型?

标签: c# wpf user-interface dispatcher


【解决方案1】:

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 时更新视图。

当您需要更新coolection 时,这不是效率。 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 属性正在被替换,并且本身无法引发任何事件

【讨论】:

    【解决方案2】:

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

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

    顺便说一句,不要用Thread.Sleep 阻塞Dispatcher 的线程。它会阻止Dispatcher 尽可能顺利地进行操作。

    【讨论】:

      【解决方案3】:

      这是DoEvents的东西,overhere

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

      甚至可能更好的https://stackoverflow.com/a/11899439/138078

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-12-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-08
        • 2020-10-27
        相关资源
        最近更新 更多