2016-09-12 10 views
0

私はWPFアプリケーションでMVVMパターンを学習しようとしています。私は(私はHttpClientをを使用していますし、それは方法が非同期ですだから、それはasyncを指定する必要があります)私のviewmodelで、この非同期メソッドを書いた:viewmodelで非同期メソッドを呼び出す方法

public async Task<Dictionary<int, BusStop>> GetBusStops() 
    { 
     var busStopDict = new Dictionary<int, BusStop>(); 
     var url = "my url"; 

     using (HttpClient client = new HttpClient()) 
     using (HttpResponseMessage response = await client.GetAsync(url)) 
     using (HttpContent content = response.Content) 
     { 
      string data = await content.ReadAsStringAsync(); 
      var regularExpression = Regex.Match(data, "\\[(.)*\\]"); 
      var result = regularExpression.Groups[0]; 

      var json = JValue.Parse(result.ToString()); 
      var jsonArray = json.ToArray(); 

      foreach (var a in jsonArray) 
      { 
       // irrelevant logic 

       busStopDict.Add(nr, bs); 
      } 
     } 

     return busStopDict; 
    } 

この方法バス停(私のモデル)を充填した辞書を返します。私はビューをコンボボックスでこの辞書をバインドしたいと思いますが、私はviewmodelのコンストラクタでこの非同期メソッドを呼び出すことができないので、私はそれを動作させることができません。何か提案はありますか?

+0

コンストラクタでメソッドを呼び出せないのはなぜですか? – Fruchtzwerg

+0

コンストラクタを非同期にすることはできませんので、そのメソッドを待つことはできません。私がそのメソッドを待っていない場合は、Dictionary <> – r9s

+0

の代わりにTask <>を返します。基本的なことは、この記事で詳しく説明しています。https://msdn.microsoft.com/en-us/雑誌/ dn630647.aspx私は以下の答えに従う代わりにそれを読むことを提案する。 – Evk

答えて

3

ロジックをviewmodelコンストラクタに書き込むことはお勧めしません。代わりに、ビューの読み込み手順を妨げないように、Loadedイベントトリガーをビューに作成します。

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 


<i:Interaction.Triggers> 
    <i:EventTrigger EventName="Loaded"> 
     <i:InvokeCommandAction Command="{Binding LoadedCommand}" /> 
    </i:EventTrigger> 
</i:Interaction.Triggers> 

は、その後、あなたのviewmodelに私は次のことをやってお勧め:

あなたのLoadedイベント

public DelegateCommand LoadedCommand { get; } 

ための次のプロパティを追加します。その後、あなたのコンストラクタ

LoadedCommand = new DelegateCommand(async() => await ExecuteLoadedCommandAsync()); 

追加でそれを割り当てますロードされたメソッドとその中でメソッドを呼び出す

private async Task ExecuteLoadedCommandAsync() 
{ 
    var busStops = await GetBusStops(); 
    //TODO: display the busStops or do something else 
} 

また、非同期メソッドに接尾辞として「Async」を追加すると、適切な名前付けパターンの名前が付けられます。どのメソッドが非同期であるかを素早く確認することができます。これは、単純なDelegateCommand実装

public class DelegateCommand : ICommand 
{ 
    private readonly Predicate<object> _canExecute; 
    private readonly Action<object> _execute; 

    public event EventHandler CanExecuteChanged; 

    public DelegateCommand(Action<object> execute) 
       : this(execute, null) 
    { 
    } 

    public DelegateCommand(Action<object> execute, 
       Predicate<object> canExecute) 
    { 
     _execute = execute; 
     _canExecute = canExecute; 
    } 

    public override bool CanExecute(object parameter) 
    { 
     if (_canExecute == null) 
     { 
      return true; 
     } 

     return _canExecute(parameter); 
    } 

    public override void Execute(object parameter) 
    { 
     _execute(parameter); 
    } 

    public void RaiseCanExecuteChanged() 
    { 
     if(CanExecuteChanged != null) 
     { 
      CanExecuteChanged(this, EventArgs.Empty); 
     } 
    } 
} 

この実装を使用するときは、以下の

にあなたのviewmodelのコンストラクタで DelegateCommandのあなたのイニシャライズを変更する必要がある

(いわゆる「GetBusStopsAsync」を「GetBusStops」に名前を変更)

LoadedCommand = new DelegateCommand(async (param) => await ExecuteLoadedCommandAsync()); 
+0

自分でDelegateCommandを実装する必要はありますか?もしそうなら、どうですか? – r9s

+0

[prism](https://msdn.microsoft.com/en-us/library/gg406140.aspx)(予測可能な結果の実績のあるプラクティス)ライブラリを使用することができます。より簡単な実装を見てください。[こちら](http://www.wpftutorial.net/delegatecommand.html) – NtFreX

+0

Execute()とCanExecute()は仮想メソッドではないので、オーバーライドできません。 – r9s

1

コンストラクタで非同期メソッドを開始し、同様に続けるアクションを定義します。

//Constructor 
public ViewModel() 
{ 
    GetBusStops().ContinueWith((BusStops) => 
    { 
     //This anonym method is called async after you got the BusStops 
     //Do what ever you need with the BusStops 
    }); 
} 

ドントあなたは

Application.Current.Dispatcher.BeginInvoke(() => 
{ 
    //Your code here 
}); 
+0

あなたのviewmodelコンストラクタにコードを書くのがよい方法ですか?また、この方法でビューの読み込み手順が妨げられることはありませんか? – NtFreX

+0

BusStopsをロードしてViewModelを初期化しています。初期化は通常、コンストラクタで行われます。このメソッドはまだ非同期なので、Viewの読み込みを妨げません。 ContinueWithはUIスレッドで実行されていないため、私の答えに示されているようにinvokeを使用する必要があります。 – Fruchtzwerg

+0

しかし、非同期である理由があります。 (これは "LoadBusStopsAsync"のように呼び出されるべきではありませんか?)これはブロックアクションです。 (それらは異なる扱いをしてはいけませんか?)私は自分のソリューションでディスパッチャーを使う必要はないと考えています – NtFreX

1

と表示に使用するプロパティにアクセスしたい場合、私はAsycLazyをチェックアウトするかAsyncCommandsをチェックアウトして作成することになり、UIスレッドを起動することを忘れ非同期タスクに基づく "LoadCommand"。コンストラクタに多くのロジックを置くべきではありません。なぜなら、デバッグするのが難しくなるため、強力に結合させる必要があり、ビューモデルのUnitテストを書くことが非常に難しくなります。私はできれば全てを怠惰にする傾向がある。

AsyncLazy
http://blog.stephencleary.com/2012/08/asynchronous-lazy-initialization.html

AsyncCommand
http://mike-ward.net/2013/08/09/asynccommand-implementation-in-wpf/

3

あなたはasynchronous data bindingは(私は、件名に記事全体を持っている)を使用する必要があります。my Mvvm.Async libraryからNotifyTaskを使用して

、それは次のようになります。

public async Task<Dictionary<int, BusStop>> GetBusStopsAsync() { ... } 
public NotifyTask<Dictionary<int, BusStop>> BusStops { get; } 

MyViewModelConstructor() 
{ 
    BusStops = NotifyTask.Create(() => GetBusStopsAsync()); 
} 

次に、あなたのビューがモデル・バインドをすることができますBusStops.Resultに(それがまだ取得されていない場合はnull)辞書を得るために、また、データビジースピナー/エラーインジケーターの場合は、BusStops.IsNotCompleted/BusStops.IsFaultedにバインドしてください。

関連する問題