如何在 MVVM 应用程序中将任意视图动态插入 xaml

本文关键字:视图 任意 动态 插入 xaml MVVM 应用程序 | 更新日期: 2023-09-27 18:33:35

我的应用程序中有多个地方,我ContentControl放置在xaml中,我事先不知道它的Content会是什么。实现此方案的最佳做法是什么?

现在我正在考虑两种方法:

  1. ContentControl.Content绑定到视图模型,并使用 DataTemplate 的字典查找合适的视图。我对这种方法的问题是,即使我要在二分法中列出所有可能的组合,在某些情况下,我也根本不知道编译时的确切视图类型(或视图模型,如果有的话(。我想,使用这种方法来托管非 WPF 内容时,我也会遇到麻烦。
  2. 创建某种接口:

    interface IContentPlugin : IDisposable
    {
        object View { get; }
    }
    

    并将ContentControl.Content直接绑定到IContentPlugin.View。然后,我可以拥有此接口的多个实现,并在需要时交换它们。但是这个解决方案并没有让我觉得它与 MVVM 应用程序很匹配,因为它迫使我在视图模型中引用IContentPlugin

您认为什么是最佳选择,为什么?也许有更好的方法?

如何在 MVVM 应用程序中将任意视图动态插入 xaml

这是一个

非常有趣的场景,对于这些情况,我通常会引入ViewResolverServiceViewModelResolverService(或两者兼而有之(。因此,可以根据view(类,类型或名称(为您提供ViewModel的东西将它们匹配以在ContentControl中托管它们。或者一种服务,它可以为您提供基于视图模型(类型或字符串名称(的视图。有了这个强大的概念,您可以使用ContentControls和/或DataTemplates,并且拥有完全控制权。

我回答了一些问题来解释这里的概念:

在视图模型

定位器中注册所有视图模型和服务

在这里:从插件获取视图和视图模型

更多在这里: https://stackoverflow.com/search?q=ViewModelResolver

因此,如果您从鸟瞰图查看它,则需要将 MVVM 应用于您的视图 ContentControls。(这些视图本身也应用了 MVVM(。

您应该通过数据模板使用隐式视图确定。

这是通过在ContentControl范围的本地ResourceDictionary中为ViewModel类型提供特定于类型的DataTemplates(即没有键引用的 DataTemplates(来实现的。

但请注意,在单个ViewModel可以有多个与之关联的Views的情况下,您需要非常仔细地确定ResourceDictionary的范围。

更新

使用隐式View确定的原因是:

  • 通常,View分辨率查找比编写 View 解析服务更快。
  • 您不会通过编写自己的 View 解析器来重复工作,然后您需要将其插入WPF运行时。

您应该告诉外部源你支持什么,在这种情况下,请始终保持WPF ResourceDictionary,以便无论内容/资源如何,您都可以将其合并到运行时ResourceDictionaries中 - 这意味着,您的外部源将需要为您提供WinForms控件包装器。

作为在使用此模式之前创建插件框架的人,使用概念上的"纯 MVVM"实现大大简化了事情 - 外部源为 VM 提供了一个ViewModel类和ResourceDictionary的资源,您可以WPF为您完成View确定的繁重工作。

将 DataTemplate 用于 ContentControls:

    <DataTemplate DataType="{x:Type vm:DataSourceViewModel}">
           <view:DataSourceView></view:DataSourceView>
      </DataTemplate>
      <DataTemplate DataType="{x:Type vm:SelectTemplateViewModel}">
           <view:SelectTemplateView></view:SelectTemplateView>
      </DataTemplate>
.........
........
    <ContentControl Margin="5"  HorizontalAlignment="Stretch" Content="{Binding CurrentPage}" Name="ImportControls"></ContentControl>

VM:是作为内容控件内容的对象类型

视图

:是要查看的特定视图,是否将特定类型的对象设置为内容控件的内容

最终,我选择了第二种方法。我能够解决我的主要问题,即:

但是这个解决方案并没有让我觉得它与 MVVM 应用程序相得益彰,因为它迫使我在视图模型中引用 IContentPlugins。

将这些"插件"传递到视图模型中是一个错误,你不应该这样做。您可以并且应该做的是找到一种方法将视图划分为较小的独立段,并以非 MVVM 方式设置其内容。所以基本上我最终得到了一个视图,它充当容器,看起来像这样:

<UserControl x:Name=this>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefiniton>
            <RowDefiniton>
            <RowDefiniton>
        </Grid.RowDefinition>
        <ContentControl Grid.Row="0" Content="{Binding PluginA.View, ElementName=this}"/>
        <ContentControl Grid.Row="1" Content="{Binding PluginB.View, ElementName=this}"/>
        <ContentControl Grid.Row="2" Content="{Binding PluginC.View, ElementName=this}"/>
    </Grid>
</UserControl>

其中PluginAPluginBPluginC是代码隐藏中的依赖项属性,由 DI 容器使用属性注入设置。我对最终结果感到满意,它给了我所需的灵活性。

您也可以使用PRISM,粗略地说,它以更通用和灵活的方式做同样的事情。不过,对于我的应用程序来说,这有点太复杂了,所以我决定保持简单。但是,如果您正在尝试解决类似的问题,则应尝试一下。