【问题标题】:How can I switch around Views/ViewModels that are provided objects which are asychronously initialized ahead of time?如何切换提供提前异步初始化的对象的视图/视图模型?
【发布时间】:2017-07-06 00:40:34
【问题描述】:

当虚拟机(以及视图)需要接受对象被异步初始化的构造函数参数时,我如何在视图/视图模型之间切换。或者,有没有更好的方法来为视图/虚拟机提供这些对象?

这是我之前在 View/VM 不使用构造函数参数时的实现。

MainWindow.xaml

当 ContentControl 发生变化时,使用 DataTemplates 在视图之间切换。

<Window.DataContext>
    <viewModels:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
    <DataTemplate DataType="{x:Type viewModels:InitializerViewModel}">
        <userControls:InitializerUserControl />
    </DataTemplate>
    <DataTemplate DataType="{x:Type viewModels:GameListingViewModel}">
        <views:GameListingView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type viewModels:StreamListingViewModel}">
        <views:StreamListingView />
    </DataTemplate>
</Window.Resources>
<ContentControl x:Name="ContentSection" Content="{Binding CurrentViewModel, Mode=OneWay}">
    <interactivity:Interaction.Triggers>
        <interactivity:EventTrigger EventName="Loaded">
            <interactivity:InvokeCommandAction Command="{Binding StartInitializationCommand}"/>
        </interactivity:EventTrigger>
    </interactivity:Interaction.Triggers>
</ContentControl>

MainWindowViewModel.cs

创建新的 ViewModel,然后将 CurrentViewModel 设置为相关的 ViewModel。当这种情况发生变化时,DataTemplate 会根据上述 DataTemplate 切换视图。

public class MainWindowViewModel : BaseViewModel
{
    private InitializerViewModel _initializerViewModel;
    public ICommand StartInitializationCommand { get; set; }    

    public MainWindowViewModel()
    {
        StartInitializationCommand = new AwaitableDelegateCommand(InitializeAsync);
    }

    private async Task InitializeAsync()
    {
        _initializerViewModel = new InitializerViewModel();
        CurrentViewModel = _initializerViewModel;
        var initializationResult = await _initializerViewModel.InitializeAsync();
        if (initializationResult)
        {
            GameListingViewModel = new GameListingViewModel(_initializerViewModel.MyObject1, _initializerViewModel.MyObject2);
            CurrentViewModel = GameListingViewModel;
        }
    }
}

GameListingView.xaml(这是我切换进出的 VM 之一):

之前在其 xaml 中定义的 DataContext via:

<UserControl.DataContext>
    <viewModels:GameListingViewModel />
</UserControl.DataContext>-->

上述所有工作都按预期工作。


现在,由于新功能,这些 ViewModel 需要接收 1 个或多个对象才能运行,所以我想将它们传递给构造函数。因此,我无法再在 xaml 中定义 DataContext,但我可以使用代码隐藏来创建它们。

public GameListingView(ConfigSettings configSettings, GameBoxHelper gameBoxHelper)
{
    InitializeComponent();
    DataContext = new GameListingViewModel(configSettings, gameBoxHelper);
}

问题是这需要 View 的构造函数也接受相关对象(否则我会从哪里获取这些对象?),这使得返回 MainWindow.xaml 的 DataTemplate 不再工作。

Xaml 错误消息:“类型 'GameListingView' 不能用作对象元素,因为它不是公共的或未定义公共无参数构造函数或类型转换器。未找到默认构造函数。”

重要的是,传递给这些 ViewModel 的对象并没有立即准备好; MainWindowViewModel 预先初始化它们,这是异步完成的。根据上述错误消息,我认为可以使用TypeConverter(将要传递的对象绑定到参数并在 xaml 中将它们分配为 TypeConverter 的参数),但我不知道这将如何工作,因为它们是没有立即准备好(异步初始化)。

供参考:目的是在加载应用程序时预先完成所有初始化(从文件系统读取配置设置,从 Web API 检索一些数据等),以便在需要时立即准备好应用程序的其余部分响应更快。我不希望将对象的异步初始化移至使用它们的 VM。

我不使用任何 MVVM 框架。

鉴于这一切,我的问题是如何继续切换我的视图/虚拟机,同时为它们提供提前异步初始化的对象?


【问题讨论】:

  • 我认为您可以在视图中使用 DependancyProperty 并在视图模型发生更改时更新它们。
  • 你能举个例子说明它是如何工作的吗?
  • 考虑以视图模型为中心的设计,其中您的主视图模型具有子视图模型(或子视图模型的集合)作为属性,并且子视图模型可能具有它们自己的子 VM 属性。与其创建自己的视图模型的视图,不如使用DataTemplates 将视图隐式分配给视图模型。 &lt;ContentControl Content="{Binding BlahVM}" /&gt;——无论BlahVM 是什么类型,如果它有一个隐含的DataTemplate,你会在UI 中得到它。 BlahVM 可以声明为对象,它的父级可以整天切换不同的子虚拟机。
  • 这不是我在前半部分已经在做的事情(已经运行的代码)吗?我遇到的问题是克服了创建虚拟机并将其附加到视图的障碍,当视图/虚拟机采用构造函数参数时(并且没有立即准备好)。

标签: c# .net wpf xaml datatemplate


【解决方案1】:

您不能在应用程序启动时在 App.xaml.cs 或引导程序类中创建依赖项,然后将它们作为构造函数参数传递给 MainViewModel 吗?

从 App.xaml 中的 Application 根元素中移除 StartupUri 属性并覆盖 App.xaml.cs 中的 OnStartup 方法:

public partial class App : Application
{
    protected async override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        var yourDependency = await YourClass.CreateAsync();

        MainWindowViewModel vm = new MainWindowViewModel(yourDependency);
        MainWindow mainWindow = new WpfApplication4.MainWindow() { DataContext = vm };
        mainWindow.Show();
    }
}

然后MainViewModel 类将负责注入它创建的其他视图模型,以及它由 App 类注入的依赖项。

或者,如果您更喜欢在 MainViewModel 类中初始化依赖项,您可以在 App 类中初始化这个,即 MainViewModel

有关异步工厂方法和异步构造的更多信息,请参阅 Stephen Cleary 的博文:http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html

【讨论】:

  • 我觉得这并不能真正解决我的问题。我之前没有明确说明的一件事是,当一切都在初始化时,我正在使用每个部分的状态更新 UI(使用在第一个代码 sn-p 中看到的InitializerViewModel & 控件)。如果我将 init 从 MainVM 移到 App 中,我将失去可视化效果,并且如果我将它与 MainVM 分离到其他东西中,我仍然需要在传递参数时将视图/VM 从处理 init 的任何东西切换回 MainVM。似乎使用这种方法,您在初始化期间只会有一个空白屏幕。
  • 另外,使用这种方法,我将如何获取现在初始化的参数并将它们传递给子虚拟机(鉴于DataTemplates 我目前用于在ContentControl VM 更改时在视图之间交换?我知道我可以将它传递给 MainVM,现在 MainVM 有了它,但是 MainVM 如何以一种便于交换的方式将它提供给它自己的子 VM?
  • MainVM 创建子视图模型,对吧?然后,您只需在创建它们时注入它们所需的任何依赖项。交换只是将 CurrentViewModel 属性设置为已初始化子视图模型的实例的问题。如果要异步初始化视图模型,当然您需要以某种方式对其进行初始化。最好的方法是使用异步工厂方法。
  • 注入他们如何,但是?一旦我将参数添加到构造函数中,我的DataTemplates 就会开始抛出错误,因为它们需要 ctor 参数。如何连接它以将参数从 init 完成的地方传递到子 VM?换句话说,我知道我可以在我的 MainVM 中创建一个 ChildVM 并新建 ChildVM(someParam) 并将其设置为 ContentControl。鉴于之前我使用DataTemplates 来完成此操作,如果没有视图上的无参数 ctor,我如何让关联的视图与 ContentControl 的 VM 一起更改。
  • 如果您希望我能够理解您在说什么,请发布您的 MainViewModel 的完整代码以及如何设置 CurrentViewModel。 DataTemplates 和视图不应该创建任何视图模型,所以我没有真正看到这个问题。还请通过代码澄清! - 你如何异步初始化你的类。
猜你喜欢
  • 1970-01-01
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
  • 2020-06-11
  • 2012-10-10
  • 1970-01-01
  • 2017-08-28
  • 2020-02-23
相关资源
最近更新 更多