如何以 MVVM 方式显示从外部程序集动态加载的 UserControl

How to show UserControl dynamically loaded from outer assembly in MVVM fashion

我正在开发 WPF MVVM 应用程序。

MainWindow VM 加载包含 UserControl 及其 ViewModel 的目标外部程序集。

我想在我的主窗口视图中显示此用户控件。

我想我应该使用 DataTemplates,但我不明白如何让它们与动态加载的类型一起工作。我没有要显示的代码,因为我不知道如何进行,欢迎任何建议。

编辑:下面是用于从程序集加载 UC 和 VM 的代码

Assembly assembly = Assembly.LoadFile(testProgramPath);
var publicTypes = assembly.GetTypes().Where(t => t.IsPublic).ToArray();
TestProgramUserControl = publicTypes.Single(t => t.BaseType.FullName == "System.Windows.Controls.UserControl");
TestProgramUserControlViewModel = publicTypes.Single(t => t.GetCustomAttribute<TestProgramUserControlViewModelAttribute>() != null);

我不能对 UC 或其 VM 做出任何假设,我想在我的 MainWindow 中显示它包含或执行的任何操作。然后,它有责任通过适当的消息与合适的接收者进行通信。

我的建议有点长,求评论。

由于您“不知道”该怎么做,这里有一些建议可以为您指明正确的方向。

Managed Extensibility Framework 专为以您描述的方式扩展应用程序的动态发现而设计。他们为您制作。

https://docs.microsoft.com/en-us/dotnet/framework/mef/

除了将您的视图和视图模型放入此程序集中,我还建议将数据模板资源字典放入其中。您可以使用它来将视图类型与视图模型类型相关联。 您可以使用 mef 或仅使用命名约定来定义此资源字典是什么。

要使资源字典可被 mef 发现,您需要在 class 后面为其添加代码。然后您可以将正确的属性应用于该属性,例如:

[Export(typeof(ResourceDictionary))]
public partial class ExternalDataTemplateResourceDictionary : ResourceDictionary
{
    public ExternalDataTemplateResourceDictionary ()
    {
        InitializeComponent();
    }
 }

要将 class 连接到您的资源字典,您可以使用与您可能在 windows 或用户控件中看到的类似的机制。您在其开始标记中使用 x:Class:

<ResourceDictionary      
       ....
       x:Class="YourProject.ExternalDataTemplateResourceDictionary "

当您发现一个 dll 时,您加载它的内容并合并它的模板资源字典。 然后父视图不需要明确知道您的 specialFoo 用户控件与它加载的这个 dll 中的 superFooViewModel 相关联。合并后的数据模板使用“标准”视图模型第一个数据类型关联来执行此操作。

感谢这里的建议和 SO WPF 聊天,我按如下方式解决了我的问题。

我添加了一个约束:我的外部程序集只能包含一个 UserControl,并且该用户控件必须将 DataTemplate 定义为具有固定名称的资源。 我的主要 VM 从外部 UC 获得具有上述固定名称的唯一资源。 我的主视图使用此 DataTemplate 作为 ContentPresenter 的 ContentTemplate。

外部用户控制的一些简化代码:

<UserControl xmlns:local="clr-namespace:MyNamespace">
    <UserControl.Resources>
        <ResourceDictionary>
            <DataTemplate x:Key="FixedKeyTemplate"
                          DataType="{x:Type local:MyOuterViewModel}">
                <StackPanel>
                    ...
                </StackPanel>
            </DataTemplate>
        </ResourceDictionary>
    </UserControl.Resources>
</UserControl>

主视图模型:

Assembly assembly = Assembly.LoadFile(testProgramPath);
var publicTypes = assembly.GetTypes().Where(t => t.IsPublic).ToArray();

Type userControlType = publicTypes.Single(t => t.BaseType.FullName == "System.Windows.Controls.UserControl");
UserControl userControlView = Activator.CreateInstance(userControlType) as UserControl;
DataTemplate userControlDataTemplate = userControlView.Resources["TestProgramGUIDataTemplate"] as DataTemplate;

Type userControlViewModelType = publicTypes.Single(t => t.GetCustomAttribute<UserControlViewModelCustomAttribute>() != null);
object userControlViewModel = Activator.CreateInstance(userControlViewModelType);

主视图:

<ContentPresenter Content="{Binding UserControlViewModel}"
                  ContentTemplate="{Binding Path=DataContext.UserControlTemplate,
                                            RelativeSource={RelativeSource Mode=FindAncestor,
                                                                           AncestorType={x:Type Window}}}"/>

@Andy的建议更“专业”,但就我对整个应用的掌控,而且我也是唯一的用户,我想我可以满足于这个更简单的解决方案。