我试图在WPF应用程序中学习MVVM模式。我在我的viewmodel中编写了这个异步方法(它必须是异步的,因为我使用HttpClient并且它的方法是异步的):
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;
}
此方法返回一个充满公交车站的字典(我的模型)。我想在视图中将这个字典与combobox绑定,但是我无法使它工作,因为我无法在我的viewmodel的构造函数中调用这个异步方法,我不知道在哪里可以调用它。你有什么建议吗?
答案 0 :(得分:4)
我不建议在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
}
此外正在添加&#34; Async&#34;作为异步方法的后缀命名一个好的命名模式。它使您可以快速查看哪些方法是异步的。 (所以重命名&#34; GetBusStops&#34;到&#34; GetBusStopsAsync&#34;)
这是一个简单的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
的初始化更改为以下
LoadedCommand = new DelegateCommand(async (param) => await ExecuteLoadedCommandAsync());
答案 1 :(得分: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
/ {{1} }繁忙的微调器/错误指示器。
答案 2 :(得分: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
});
}
如果要访问用于View with
的属性,请不要忘记调用UI线程Application.Current.Dispatcher.BeginInvoke(() =>
{
//Your code here
});
答案 3 :(得分:1)
我会查看 AsycLazy 或查看 AsyncCommands 并创建一个基于异步任务&#34; LoadCommand&#34;。你不应该在构造函数中加入太多的逻辑,因为它会使调试变得更难,迫使你强烈耦合,并且很难为你的视图模型编写单元测试。如果可以的话,我倾向于把一切变得懒惰。
AsyncLazy
http://blog.stephencleary.com/2012/08/asynchronous-lazy-initialization.html
AsyncCommand
http://mike-ward.net/2013/08/09/asynccommand-implementation-in-wpf/