从后台线程更新DataContext

问题描述 投票:3回答:3

我在后台线程中获取wpf窗口的数据,如[带有async / await的framework 4.0]:

async void refresh()
{
    // returns object of type Instances
    DataContext = await Task.Factory.StartNew(() => serviceagent.GetInstances());
    var instances = DataContext as Instances;
    await Task.Factory.StartNew(() => serviceagent.GetGroups(instances));
    // * problem here * instances.Groups is filled but UI not updated
}

当我在GetInstances中包含GetGroups的操作时,UI会显示组。 当我在单独的操作中更新时,DataContext包含组correclty,但UI不显示它们。

GetGroups()方法中,我将NotifyCollectionChangedAction.Reset包含在ObservableCollection组中,这无济于事。 更奇怪的是,我只在列表上调用NotifyCollectionChangedAction.Reset一次,但执行了三次,而列表有十个项目?!

我可以写下来解决这个问题:

DataContext = await Task.Factory.StartNew(() => serviceagent.GetGroups(instances));

但这是通过后台进程更新DataContext和UI的常规方法吗? 实际上我只想更新现有的DataContext而不重新设置它?

编辑:serviceagent.GetGroups(instances)更详细:

public void GetGroups(Instances instances)
{
    // web call
    instances.Admin = service.GetAdmin();

    // set groups for binding in UI
    instances.Groups = new ViewModelCollection<Groep>(instances.Admin.Groups);

    // this code has no effect
    instances.Groups.RaiseCollectionChanged();
}

这里ViewModelCollection<T>继承自ObservableCollection<T>,我添加了方法:

public void RaiseCollectionChanged()
{
    var handler = CollectionChanged;
    if (handler != null)
    {
        Trace.WriteLine("collection changed");
        var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
        handler(this, e);
    }
}
wpf async-await ui-thread
3个回答
4
投票

在代码的async部分中有几点突出:

基于这些,我还推荐我的intro to async博客文章。

关于实际问题......

从后台线程更新数据绑定代码总是很棘手。我建议您将ViewModel数据视为UI的一部分(可以这么说,它是一个“逻辑UI”)。因此,在后台线程上检索数据很好,但是应该在UI线程上更新实际的VM值。

这些更改使您的代码看起来更像这样:

async Task RefreshAsync()
{
  var instances = await TaskEx.Run(() => serviceagent.GetInstances());
  DataContext = instances;
  var groupResults = await TaskEx.Run(() => serviceagent.GetGroups(instances));
  instances.Admin = groupResults.Admin;
  instances.Groups = new ObservableCollection<Group>(groupResults.Groups);
}

public GroupsResult GetGroups(Instances instances)
{
  return new GroupsResult
  {
    Admin = service.GetAdmin(),
    Groups = Admin.Groups.ToArray(),
  };
}

接下来你需要检查的是Instances是否实现了INotifyPropertyChanged。设置Reset时,您不需要提高Groups集合更改事件;因为GroupsInstances的财产,所以Instances负责筹集INotifyPropertyChanged.PropertyChanged

或者,您可以最后设置DataContext

async Task RefreshAsync()
{
  var instances = await TaskEx.Run(() => serviceagent.GetInstances());
  var groupResults = await TaskEx.Run(() => serviceagent.GetGroups(instances));
  instances.Admin = groupResults.Admin;
  instances.Groups = new ObservableCollection<Group>(groupResults.Admin.Groups);
  DataContext = instances;
}

4
投票

似乎对DataContext的含义有点混淆。 DataContext不是您必须更新的特殊对象。它是对要绑定到UI的对象的引用。每当您对这些对象进行更改时,都会通知UI(如果您实现了正确的接口)。

因此,除非您明确更改DataContext,否则您的UI无法猜测现在您想要显示不同的对象集。

实际上,在您的代码中,没有理由将DataContext设置两次。只需使用要显示的最终对象集进行设置即可。实际上,由于您处理相同的数据,因此没有理由使用两个任务:

async Task refresh()
{
    // returns object of type Instances
    DataContext=await Task.Factory.StartNew(() => {
             var instances = serviceagent.GetInstances();
             return serviceagent.GetGroups(instances);
    });
}

注意:

你应该使用async void签名。它仅用于即发即弃事件处理程序,您不关心它们是成功还是失败。原因是无法等待async void方法,因此没有人能够知道它是否成功。


1
投票

我发现RaiseCollectionChangedGroups所绑定的DataContext属性没有影响。我只需要通知:instances.RaisePropertyChanged("Groups");

© www.soinside.com 2019 - 2024. All rights reserved.