MVVM视图模型使用async时如何设置数据上下文
本文关键字:置数据 上下文 async 视图 模型 MVVM | 更新日期: 2023-09-27 17:53:44
经过几个小时的搜索,我仍然没有回答这个问题。我读了这篇关于异步MVVM的好文章,并使我的视图模型使用工厂方法。
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中的数据上下文。我不能简单地写
<Page.DataContext>
<viewModel:MainViewModel/>
</Page.DataContext>
因为我应该使用MainViewModel.CreateAsync()-method。我不能在代码隐藏中做,我甚至想做,因为代码隐藏构造函数是普通方法,而不是异步方法。那么,哪一种方式是正确的呢?
使我的视图模型使用工厂方法
我通常是这种方法的粉丝——这是我最喜欢的绕过"无异步构造函数"限制的方法。但是,它在MVVM模式下不能很好地工作。
这是因为从逻辑上讲,vm就是你的UI。当用户在应用程序中导航到一个屏幕时,应用程序需要立即响应(同步)。它不一定要显示任何有用的东西,但它确实需要显示一些的东西。因此,虚拟机的构建必须是同步的。
所以,与其尝试异步构建你的VM,不如先决定你想要"正在加载"或"不完整"的UI是什么样子。你的(同步)VM构造函数应该初始化为那个状态,并且它可以启动一些异步工作,当完成时更新VM。
手工操作并不太难,或者您可以使用我在MSDN的一篇关于异步MVVM数据绑定的文章中描述的NotifyTaskCompletion
方法来使用数据绑定驱动状态转换。
必须在打开窗口之前初始化视图模型。转到您的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();
}
我会重新考虑。使MainViewModel
的构造/实例化轻量级。然后在虚拟机上创建Load
或Initialize
方法。从代码后面创建一个实例,将其设置为DataContext
,然后调用init方法并让它运行。
。
/// <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));
}
}
请注意,这是一个快速的解决方案,其他两个答案(与依赖注入框架配对)将为您的解决方案提供适当的高级结构。
首先,您应该将默认构造函数设置为私有,以避免滥用您的类(您引用的文章就是这样做的——构造函数是 private
)。
你用来设置DataContext
的方法不适合MVVM
模式(View
不应该自己创建ViewModel
)。你应该在高级图层中创建View
和ViewModel
,并让该图层绑定它们。表示如果Page
是您的主要View
,您应该通过覆盖OnStartup
在App.xaml.cs
中创建它们,像这样:
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();