ビューモデルがすでに定義されているデフォルトのテンプレートを使用して電話アプリを起動しました。 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;
}
}
実行を押しても何も表示されません...何かが足りませんか?どんなポインタでも大歓迎です。
わかりました。簡単な答えは、INotifyPropertyChanged
および/またはItems
セッターでIsDataLoaded
通知が欠落している可能性があるということです。
長い答えは少しかかります。 :)
まず、async void
を避ける必要があります。理由については、私の 非同期プログラミングのベストプラクティス の記事で詳しく説明しています。この場合、エラー処理を検討してください。あなたの幸せなケースが「呼び出しが正常に戻った」ときであるのは素晴らしいことですが、悲しいケースはあなたのプログラムを破壊するでしょう。
それでは、すべてを可能な限り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
にバインドできます。