异步加载ViewModel中的数据(使用async和await)不能使用数据绑定

时间:2013-03-08 23:37:52

标签: c# data-binding mvvm windows-phone-8 async-await

我开始使用默认模板的手机应用程序,该模板已经定义了视图模型。我修改了MainViewModel的LoadData()方法,以异步方式调用odata服务。但它不适用于数据绑定。我已验证调用已成功返回,但未显示任何结果。

LongListSelector的items源绑定到视图模型中的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>

这是我对视图模型的修改(注意异步和等待使用):

public void LoadData()
    {
        FetchTileViewItems();        
    }

    private async void FetchTileViewItems()
    {
        var ret = await I2ADataServiceHelper.GetTileViewItemsAsync();
        this.Items = new ObservableCollection<TileViewItem>(ret);
        this.IsDataLoaded = true;
    }

我在页面上的NavigatedTo事件中调用LoadData()方法,就像之前一样:

protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (!App.ViewModel.IsDataLoaded)
            {
                App.ViewModel.LoadData();
                pr1.IsVisible = false;
            }
        }

命中运行,没有任何显示......我错过了什么吗?任何指针都非常感谢。

1 个答案:

答案 0 :(得分:34)

好的,快速回答是您可能在INotifyPropertyChanged和/或Items设置者上丢失了IsDataLoaded次通知。

答案越长越好。 :)

首先,你应该避免async void。我在Best Practices in Asynchronous Programming文章中详细说明了原因。在这种情况下,请考虑您的错误处理。很高兴你的快乐案例是“电话成功回复”,但悲伤的情况会破坏你的节目。

所以,让我们尽可能地将所有内容重写为async Task,并在我们进行时follow the *Async convention重写:

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();
    }
}

这是编写async代码的更自然的方法。

接下来,让我们解决这个错误情况。您可以try中执行catch / OnNavigatedTo

protected override async void OnNavigatedTo(NavigationEventArgs e)
{
    try
    {
        if (!App.ViewModel.IsDataLoaded)
        {
            await App.ViewModel.LoadDataAsync();
        }
    }
    catch (Exception ex)
    {
        ...
    }
}

但我实际上更倾向于以ViewModel为中心,数据绑定友好的系统来进行错误处理。这样,“断开连接”对于您的应用来说是一个非常自然的状态;即使它只是显示错误消息,您的应用程序最终也会被设计用于偶尔连接的系统(即电话)。此外,生成的代码更易于测试。

我在几篇博文中描述了这种方法:我在async构建器的帖子中覆盖了asynchronous initialization pattern,在async的帖子中覆盖了data-binding in particular属性。我写了一个名为TaskCompletionNotifier的辅助类,它允许您使用Task进行数据绑定。

将这些设计放在适当位置,您的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);
    }
}

(假设您要开始在构造函数中加载数据。)

然后,您可以直接绑定到Items,也可以绑定到Initialization.IsSuccessfullyCompleted以获取快乐案例,Initialization.IsFaultedInitialization.ErrorMessage以查找悲伤案例等。< / p>