C# 在 ViewModel 中异步加载数据(使用 async 和 await)不适用于数据绑定
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15305189/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Loading data in ViewModel asynchronously (with async and await) not working with databinding
提问by user2137225
I started a phone app with the default template which has a view model already defined. I modified the MainViewModel's LoadData() method to call an odata service asynchronously. But it is not working with the databinding. I have verified that the call returned successfully but no result is displayed.
我使用默认模板启动了一个电话应用程序,该模板已经定义了一个视图模型。我修改了 MainViewModel 的 LoadData() 方法以异步调用 odata 服务。但它不适用于数据绑定。我已验证调用成功返回,但未显示任何结果。
The LongListSelector's items source is bound to the Items property in the view model.
LongListSelector 的项目源绑定到视图模型中的 Items 属性。
<phone:LongListSelector ItemsSource="{Binding Items}" x:Name="MainLongListSelector" Margin="0,0,-12,0" SelectionChanged="MainLongListSelector_SelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding UnReadCount}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding description}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
Here's my modification to the view model (note the async and await usage):
这是我对视图模型的修改(注意 async 和 await 的用法):
public void LoadData()
{
FetchTileViewItems();
}
private async void FetchTileViewItems()
{
var ret = await I2ADataServiceHelper.GetTileViewItemsAsync();
this.Items = new ObservableCollection<TileViewItem>(ret);
this.IsDataLoaded = true;
}
And I'm calling the LoadData() method in the NavigatedTo event on the page just like before:
我像以前一样在页面上的 NavigatedTo 事件中调用 LoadData() 方法:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
pr1.IsVisible = false;
}
}
Hit run and nothing shows up...Am I missing anything? Any pointers are greatly appreciated.
点击运行,什么也没有出现......我错过了什么吗?任何指针都非常感谢。
回答by Stephen Cleary
OK, the quick answer is that you're probably missing INotifyPropertyChanged
notifications on your Items
and/or IsDataLoaded
setters.
好的,快速回答是您可能错过了INotifyPropertyChanged
关于您Items
和/或IsDataLoaded
setter 的通知。
The longer answer will take a bit. :)
更长的答案需要一点时间。:)
First, you should avoid async void
. I describe why in detail in my Best Practices in Asynchronous Programmingarticle. In this case, consider your error handling. It's nice that your happy case is when "the call returned successfully" but the sad case will tear your program up.
首先,你应该避免async void
. 我在我的异步编程最佳实践文章中详细描述了原因。在这种情况下,请考虑您的错误处理。很高兴你的快乐案例是“调用成功返回”,但悲伤的案例会撕裂你的程序。
So, let's rewrite everything as async Task
as much as possible, and follow the *Async
conventionwhile we're at it:
因此,让我们async Task
尽可能地重写所有内容,并在我们进行时遵循*Async
约定:
public async Task LoadDataAsync()
{
await FetchTileViewItemsAsync();
}
private async Task FetchTileViewItemsAsync()
{
var ret = await I2ADataServiceHelper.GetTileViewItemsAsync();
this.Items = new ObservableCollection<TileViewItem>(ret);
this.IsDataLoaded = true;
}
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
await App.ViewModel.LoadDataAsync();
}
}
This is the more natural way to write async
code.
这是编写async
代码的更自然的方式。
Next, let's fix up that error situation. You cando a try
/catch
in OnNavigatedTo
:
接下来,让我们修复那个错误情况。你可以做一个try
/ catch
in OnNavigatedTo
:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
try
{
if (!App.ViewModel.IsDataLoaded)
{
await App.ViewModel.LoadDataAsync();
}
}
catch (Exception ex)
{
...
}
}
But I actually lean more towards a ViewModel-centric, databinding-friendly system for error handling. That way, "disconnected" is a perfectly natural state for your application; even if all it does is display an error message, your application ends up being designedfor a occasionally-connected system (i.e., a phone). Also, the resulting code is more testable.
但实际上,我更倾向于使用以 ViewModel 为中心、对数据绑定友好的错误处理系统。这样,“断开连接”对于您的应用程序来说是一个非常自然的状态;即使它是所有显示错误消息时,应用程序最终被设计用于偶尔连接系统(即,电话)。此外,生成的代码更易于测试。
I describe this approach in a couple of my blog posts: I cover the asynchronous initialization patternin my post on async
constructors, and the data-binding in particularin my post on async
properties. I wrote a helper class called TaskCompletionNotifier
which enables you to use Task
with data binding.
我在几篇博文中描述了这种方法:我在关于构造函数的文章中介绍了异步初始化模式,在我关于属性的文章中介绍async
了数据绑定async
。我编写了一个名为的帮助程序类TaskCompletionNotifier
,它使您能够Task
与数据绑定一起使用。
Putting these designs in place, your ViewModel code ends up looking more like this:
将这些设计放在适当的位置,您的 ViewModel 代码最终看起来更像这样:
public sealed class MyViewModel : INotifyPropertyChanged
{
public ObservableCollection<TileViewItem> Items
{
get { return _items; }
private set { _items = value; RaisePropertyChanged(); }
}
public ITaskCompletionNotifier Initialization { get; private set; }
public MyViewModel()
{
Initialization = TaskCompletionNotifierFactory.Create(InitializeAsync());
}
private async Task InitializeAsync()
{
var ret = await I2ADataServiceHelper.GetTileViewItemsAsync();
this.Items = new ObservableCollection<TileViewItem>(ret);
}
}
(This is assuming you want to start loading data in the constructor.)
(这是假设您要开始在构造函数中加载数据。)
You can then bind to Items
directly, and you can also bind to Initialization.IsSuccessfullyCompleted
for the happy case, Initialization.IsFaulted
and Initialization.ErrorMessage
for the sad case, etc.
然后,您可以绑定到Items
直接,你也可以绑定到Initialization.IsSuccessfullyCompleted
对幸福的情况下,Initialization.IsFaulted
并Initialization.ErrorMessage
为悲伤的情况,等等。