通过调度程序/数据绑定在此更新UI中缺少什么

时间:2018-05-25 07:50:48

标签: 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更新?

3 个答案:

答案 0 :(得分:2)

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作为数据绑定源。您可以使用任何对象,包括数组或List。在这种情况下,您必须在代码中引发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"));
    }
}

这将告诉视图加载所有内容并再次显示它们。当你只想显示从数据库加载的数据而不修改它们时,这是完全没问题的,因为它使得映射实体更容易实现ViewModels。在这种情况下,您只需在作为命令结果附加新ViewModel时更新视图。

当你需要更新calection时,这有效。 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属性已被替换,并且不能自行引发任何事件

答案 1 :(得分:1)

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

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

顺便说一句,请勿使用Dispatcher阻止Thread.Sleep的主题。它可以防止Dispatcher尽可能顺利地完成任务。

答案 2 :(得分:0)

这是DoEvents事,overhere

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

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

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