MVVM如何在viewmodel使用async时设置datacontext

时间:2015-06-06 10:16:55

标签: c# asynchronous mvvm windows-phone-8.1

经过几个小时的搜索,我仍然没有回答这个问题。我已经阅读了关于异步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() - 方法。我无法在代码隐藏方面做到这一点,我甚至想做,因为代码隐藏 - 构造函数是常规方法,而不是异步方法。那么哪种方法可以继续?

4 个答案:

答案 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本身)。 您应该在更高级别的图层中创建ViewViewModel并让该图层绑定它们。如果Page是您的主要View,则应通过覆盖App.xaml.csOnStartup中创建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上创建LoadInitialize方法。从代码隐藏创建一个实例,将其设置为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));
    }
}

请注意,这是一个快速而肮脏的解决方案,另外两个答案(与依赖注入框架配对)将为您的解决方案提供适当的高级结构。