如何在 MVVM 应用程序中将任意视图动态插入 xaml
本文关键字:视图 任意 动态 插入 xaml MVVM 应用程序 | 更新日期: 2023-09-27 18:33:35
我的应用程序中有多个地方,我ContentControl
放置在xaml中,我事先不知道它的Content
会是什么。实现此方案的最佳做法是什么?
现在我正在考虑两种方法:
- 将
ContentControl.Content
绑定到视图模型,并使用DataTemplate
的字典查找合适的视图。我对这种方法的问题是,即使我要在二分法中列出所有可能的组合,在某些情况下,我也根本不知道编译时的确切视图类型(或视图模型,如果有的话(。我想,使用这种方法来托管非 WPF 内容时,我也会遇到麻烦。 -
创建某种接口:
interface IContentPlugin : IDisposable { object View { get; } }
并将
ContentControl.Content
直接绑定到IContentPlugin.View
。然后,我可以拥有此接口的多个实现,并在需要时交换它们。但是这个解决方案并没有让我觉得它与 MVVM 应用程序很匹配,因为它迫使我在视图模型中引用IContentPlugin
。
您认为什么是最佳选择,为什么?也许有更好的方法?
非常有趣的场景,对于这些情况,我通常会引入ViewResolverService
或ViewModelResolverService
(或两者兼而有之(。因此,可以根据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>
其中PluginA
、PluginB
和PluginC
是代码隐藏中的依赖项属性,由 DI 容器使用属性注入设置。我对最终结果感到满意,它给了我所需的灵活性。
您也可以使用PRISM,粗略地说,它以更通用和灵活的方式做同样的事情。不过,对于我的应用程序来说,这有点太复杂了,所以我决定保持简单。但是,如果您正在尝试解决类似的问题,则应尝试一下。