经过几个小时的搜索,我仍然没有回答这个问题。我已经阅读了关于异步MVVM的好writing并使我的viewmodel使用工厂方法。
public class MainViewModel
{
// sic - public, contrary to the pattern in the article I cite
// so I can create it in the Xaml as below
public MainViewModel()
{
}
private async Task InitializeAsync()
{
await DoSomethingAsync();
}
public static async Task<MainViewModel> CreateAsync()
{
var ret = new MainViewModel();
await ret.InitializeAsync();
return ret;
}
}
这对我来说很清楚,但我不能理解如何制作MainViewModel的实例并将其设置为MainPage中的datacontext。我不能简单地写
<Page.DataContext>
<viewModel:MainViewModel/>
</Page.DataContext>
因为我应该使用MainViewModel.CreateAsync() - 方法。我无法在代码隐藏方面做到这一点,我甚至想做,因为代码隐藏 - 构造函数是常规方法,而不是异步方法。那么哪种方法可以继续?
答案 0 :(得分:6)
使我的viewmodel使用工厂方法
我通常是这种方法的粉丝 - 这是我最喜欢的解决“无异构构造函数”限制的方法。但是,它在MVVM模式中不能很好地工作。
这是因为从逻辑上讲,虚拟机是你的用户界面。当用户导航到应用中的屏幕时,应用需要立即响应 (同步)。它不一定要显示任何有用的东西,但它确实需要显示某些东西。因此,VM构造必须是同步的。
因此,不要试图异步构建您的VM,而是首先确定您希望“加载”或“不完整”的UI看起来像什么。您的(同步)VM构造函数应该初始化为该状态,并且它可以在完成时启动更新 VM的一些异步工作。
手动操作并不难,或者您可以使用我在MSDN article on async MVVM data binding中描述的NotifyTaskCompletion
方法来使用数据绑定来驱动状态转换。
答案 1 :(得分:3)
您必须在打开窗口之前初始化viewmodel。转到您的App.xaml
文件并删除该部分:StartupUri="MainWindow.xaml"
。然后你转到App.xaml.cs
并添加:
protected async override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var mainWindow = new MainWindow { DataContext = await CreateAsync() };
mainWindow.Show();
}
答案 2 :(得分:2)
首先,您应该将默认构造函数设置为私有,以避免滥用您的类(您引用的文章是这样做的 - 构造函数是 private
)。
您用于设置DataContext
的方法不适合MVVM
模式(View
不应创建其ViewModel
本身)。
您应该在更高级别的图层中创建View
和ViewModel
并让该图层绑定它们。如果Page
是您的主要View
,则应通过覆盖App.xaml.cs
在OnStartup
中创建var page = new Page();
var dataService = new YourDataService(); // iff Create or the ctor require arguments
var viewModel = await MainViewModel.CreateAsync(dataService);
page.DataContext = viewModel;
page.Show();
,如下所示:
<? echo $this->Html->css('jquery-ui');
echo $this->Html->script('jquery-1.10.2');
echo $this->Html->script('jquery-ui');
echo $this->Html->css('style.css');
?>
<script>
$(function() {
var availableTags = [<?=$cities?>];
$( "#city_from" ).autocomplete({
source: availableTags
});
});
</script>
<?php echo $this->Form->input("city_from", array('required'=>'true', 'class'=>'form-control', 'id'=>'city_from', 'placeholder'=>'City or airport', 'label'=>'<span class="opensans size13">Flying from</span>'));?>
答案 3 :(得分:2)
我会重新考虑因素。使MainViewModel
构造/实例化轻量级。然后在VM上创建Load
或Initialize
方法。从代码隐藏创建一个实例,将其设置为DataContext
,然后调用init方法让它运行。
E.g。
/// <summary>Interaction logic for MainWindow.xaml</summary>
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
var dc = new MainViewModel();
dc.Initialize("Hello", " ", "world");
this.DataContext = dc;
}
}
public class MainViewModel
{
/// <summary>Simple constructor</summary>
public MainViewModel() { }
public void Initialize(params object[] arguments)
{
//use the task to properly start a new thread as per:
//http://stackoverflow.com/a/14904107/1144090 and
//https://msdn.microsoft.com/en-us/library/hh965065.aspx
//(what would happen if we simply invoke init async here?)
this.InitializeAsync(arguments)
.ContinueWith(result =>
{
if (!result.IsFaulted)
return;
MessageBox.Show("Unexpected error: " + Environment.NewLine + result.Exception.ToString());
});
}
private async Task InitializeAsync(params object[] arguments)
{
await Task.Delay(2333);
MessageBox.Show(String.Concat(arguments));
}
}
请注意,这是一个快速而肮脏的解决方案,另外两个答案(与依赖注入框架配对)将为您的解决方案提供适当的高级结构。