我在后台线程中获取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));
一次,但执行了三次,而列表有十个项目?!
我可以写下来解决这个问题:
serviceagent.GetGroups(instances)
但这是通过backgound进程更新DataContxt和UI的常规方法吗? 实际上我只想更新现有的DataContext而不再设置它?
编辑: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);
}
}
,我添加了方法:
{{1}}
答案 0 :(得分: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 :(得分:4)
在代码的async
部分中有几点突出:
async void
。总之,void
是async
方法的非自然返回类型,因此它有一些怪癖,特别是在异常处理方面。TaskEx.Run
over StartNew
for asynchronous tasks,正如我在博客上解释的那样。基于这些,我还推荐我的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
集合已更改事件;由于Groups
是Instances
上的属性,因此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;
}
答案 2 :(得分:1)
我发现RaiseCollectionChanged
对Groups
所绑定的属性DataContext
没有影响。我只需要通知:instances.RaisePropertyChanged("Groups");
。