Подтвердить что ты не робот

Загрузка данных в ViewModel асинхронно (с асинхронным и ожидающим), не работающим с привязкой данных

Я запустил приложение для телефона с шаблоном по умолчанию, который уже имеет модель представления. Я изменил метод MainViewModel LoadData() для асинхронного вызова службы odata. Но он не работает с привязкой данных. Я подтвердил, что вызов вернулся успешно, но результат не отображается.

Источник объектов 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>

Здесь моя модификация модели представления (обратите внимание на асинхронное и ожидаемое использование):

public void LoadData()
    {
        FetchTileViewItems();        
    }

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

И я вызываю метод LoadData() в событии NavigatedTo на странице, как и раньше:

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

Хит бежит, и ничего не появляется... Я что-то пропустил? Любые указатели приветствуются.

4b9b3361

Ответ 1

ОК, быстрый ответ заключается в том, что вы, вероятно, не пропускаете уведомления INotifyPropertyChanged в настройках Items и/или IsDataLoaded.

Более длинный ответ займет немного.:)

Во-первых, вы должны избегать async void. Я подробно описываю, почему в статье Best Practices in Asynchronous Programming. В этом случае рассмотрите обработку ошибок. Приятно, что ваш счастливый случай - когда "звонок вернулся успешно", но печальный случай будет разорвать вашу программу вверх.

Итак, перепишите все как 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();
    }
}

Это более естественный способ написать код 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 и привязка данных в частности в моем сообщении в свойствах async. Я написал класс-помощник 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.IsFaulted и Initialization.ErrorMessage для печального случая и т.д.