2016-09-12 48 views
0

我正在嘗試學習WPF應用程序中的MVVM模式。我在視圖模型寫了這個異步方法(它必須是異步,因爲我使用的HttpClient和它的方法是異步):如何調用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

構造函數不能是異步的,所以我不能等待該方法。如果我不等待該方法,則返回Task <>而不是Dictionary <> – r9s

+0

基本上,您需要了解的所有內容在本文中都有詳細描述: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> 

然後在您的視圖模型我建議做以下幾點:

添加以下屬性爲您加載的事件

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 
} 

此外,將「異步」作爲後綴添加到您的異步方法命名一個良好的命名模式。它使您能夠快速查看哪些方法是異步的。 (所以重命名「GetBusStops」到「GetBusStopsAsync」)

這是一個簡單的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); 
     } 
    } 
} 

當使用這個實現,你需要在你的視圖模型構造改變你的DelegateCommand首次執行以下

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

我是否必須自己實現DelegateCommand?如果是這樣,怎麼樣? – r9s

+0

您可以使用[prism](https://msdn.microsoft.com/en-us/library/gg406140.aspx)(經過驗證的可預測結果實踐)庫。或者看看一個更簡單的實現[here](http://www.wpftutorial.net/delegatecommand.html) – NtFreX

+0

值得注意的是,方法Execute()和CanExecute()不能被重寫,因爲它們不再是虛擬方法。 – r9s

1

在構造函數中啓動異步方法,並定義一個動作以繼續使用like。

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

不要忘了調用UI線程,如果你想與

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

在你的viewmodel構造函數中編寫代碼是一個好習慣嗎?也不能以這種方式干擾視圖的加載過程嗎? – NtFreX

+0

您正在初始化您的ViewModel並加載BusStops。初始化通常在構造函數中完成。因爲該方法仍然是異步的,所以您不會打擾View的加載。由於ContinueWith沒有在UI線程中運行,因此您可能必須使用我的答案中所示的調用。 – Fruchtzwerg

+0

但是有一個原因,它是異步我supose。 (不應該被稱爲更像「LoadBusStopsAsync」?)這是一個阻止行動。 (不應該那些被處理不同?),我認爲在我的解決方案中,您不需要使用調度程序 – NtFreX

1

訪問用於查看的屬性我想看看AsycLazy或退房AsyncCommands創造一個基於異步任務的「LoadCommand」。你不應該把太多的邏輯放到構造器中,因爲它會使調試變得更加困難,迫使你強烈耦合,並且會使你爲視圖模型編寫單元測試變得非常困難。如果可以的話,我傾向於使一切都變得懶惰。

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拿到字典(或空,如果它尚未檢索),也數據 - 綁定到BusStops.IsNotCompleted/BusStops.IsFaulted繁忙的spinners /錯誤指示器。