如何以 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的建议更“专业”,但就我对整个应用的掌控,而且我也是唯一的用户,我想我可以满足于这个更简单的解决方案。
我正在开发 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的建议更“专业”,但就我对整个应用的掌控,而且我也是唯一的用户,我想我可以满足于这个更简单的解决方案。