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视图模型使用async时如何设置数据上下文

使我的视图模型使用工厂方法

我通常是这种方法的粉丝——这是我最喜欢的绕过"无异步构造函数"限制的方法。但是,它在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的构造/实例化轻量级。然后在虚拟机上创建LoadInitialize方法。从代码后面创建一个实例,将其设置为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)。你应该在高级图层中创建ViewViewModel,并让该图层绑定它们。表示如果Page是您的主要View,您应该通过覆盖OnStartupApp.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();