【问题标题】:Load WPF UI into MVVM application from plug-in assembly从插件程序集将 WPF UI 加载到 MVVM 应用程序中
【发布时间】:2012-08-30 20:03:28
【问题描述】:

我正在开发一个使用插件架构来扩展其功能的应用程序。从插件加载 WPF UI 的最佳方式是什么?

我将有一个列表框列出所有可用的插件。选择插件后,插件中定义的 WPF UI 应显示在 ContentControl 中。我想到的选项包括:

  • 需要创建一个实现特定接口的UserControl。我认为这将使插件创建变得容易。实现一个接口,你就可以开始了。我对这种方法的问题是如何将UserControl 动态加载到ContentControl 中。另外,由于我使用的是 MVVM 设计模式,似乎DataTemplateUserControl 更受欢迎。
  • 允许从插件加载DataTemplate。我相信这需要插件包含一个以某种方式命名的 XAML 文件。我的应用程序会将DataTemplate 读入我的资源字典中,如in this question. 所示。我已经看到很多类似的问题,除了它们通常只需要加载一个额外的预定义程序集即可从中获取DataTemplates。这个问题需要在任意数量的未知程序集中搜索DataTemplates

如果我选择第二个选项,我想我可以选择DataTemplate,类似于this answer describes.

你认为哪种方法更好?或者您有更好的方法来实现这一点?

【问题讨论】:

    标签: c# wpf mvvm


    【解决方案1】:

    我做了类似DataTemplates 提到的事情。我使用MEF 加载插件,然后在启动时加载Dictionary 并引用ViewModelView。该插件使用 3 个主要组件构建。

    IBasePlugin.cs

    这个简单的界面允许我们为插件创建一个骨架。这将只包含非常基础的内容,因为这是我们将使用Import 插件到我们的主应用程序使用MEF

    public interface IBasePlugin
    {
        WorkspaceViewModel ViewModel { get; }
        ResourceDictionary View{ get; }
    }
    

    Plugin.cs

    下一部分是Plugin.cs 文件。它包含我们插件的所有属性,以及所有必要的参考;比如我们的View & ViewModel

    [Export(typeof(IBasePlugin))]
    public class Plugin : IBasePlugin
    {
        [Import]
        private MyPluginViewModel _viewModel { get; set; }
        private ResourceDictionary _viewDictionary = new ResourceDictionary();
    
        [ImportingConstructor]
        public Plugin()
        {
            // First we need to set up the View components.
            _viewDictionary.Source =
                new Uri("/Extension.MyPlugin;component/View.xaml",
                UriKind.RelativeOrAbsolute);
        }
    
        ....Properties...
    
    }
    

    View.xaml

    这是一个DataTemplate,包含对插件ViewViewModel 的引用。这就是我们将使用 Plugin.cs 加载到主应用程序中的内容,以便应用程序和 WPF 知道如何将所有内容绑定在一起。

    <DataTemplate DataType="{x:Type vm:MyPluginViewModel}">
        <vw:MyPluginView/>
    

    然后我们使用MEF 加载所有插件,将它们提供给我们负责处理插件的工作区ViewModel,并将它们存储在一个ObservableCollection 中,用于显示所有可用的插件。

    我们用来加载插件的代码可能看起来像这样。

    var plugins = Plugins.OrderBy(p => p.Value.ViewModel.HeaderText);
    foreach (var app in plugins)
    {
        // Take the View from the Plugin and Merge it with,
        // our Applications Resource Dictionary.
        Application.Current.Resources.MergedDictionaries.Add(app.Value.View)
    
        // THen add the ViewModel of our plugin to our collection of ViewModels.
        var vm = app.Value.ViewModel;
        Workspaces.Add(vm);
    }
    

    一旦DictinoaryViewModel 都从我们的插件加载到我们的应用程序中,我们可以使用例如TabControl 来显示集合。

    <TabControl ItemsSource="{Binding Workspaces}"/>
    

    我也给出了类似的答案here 以及一些您可能会感兴趣的其他细节。

    【讨论】:

    • 谢谢!这会有很大帮助。我没有想到简单地在插件中包含一个ResourceDictionary。另外,我不知道 MEF 是什么。我希望我能早点发现它!这样我就不必编写自己的(低于标准的)插件架构了!
    • 我正在尝试实现此功能,但无论我做什么,在尝试加载 xaml 文件时都会出现此错误。 Could not load file or assembly 'MefTest.Plugin1.dll, Culture=neutral' or one of its dependencies. The system cannot find the file specified. Plugin1.cs 文件位于 ClassLibrary 类型的单独项目中。
    【解决方案2】:

    听起来您正在寻找的东西已经用 Prism 完成了。您定义区域,然后在运行时加载可能有也可能没有这些区域的视图的模块。如果您的所有应用程序都是针对从 Prism 派生的模块化概念构建的,则此方法有效。还有其他的,但是 Prism 已经非常广泛地处理了这个问题。

    【讨论】:

    • 我将不得不深入研究 Prism。我刚刚在 Prism 上摸了摸表面,到目前为止,我才真正将它用于DelegateCommand。现在我倾向于 MEF 解决方案,因为我喜欢插件创建的简单性。我想我需要重新设计才能使用 Prism。
    • 我同意 Prism 很棒。我选择我的解决方案的唯一原因是因为我想要一种简单的方法来集成现有的应用程序。因为有了这个,我可以简单地将 Plugin.cs 和 View.xaml 添加到现有应用程序中,并且只需进行一些小的更改即可将其作为插件添加到我的应用程序中。
    【解决方案3】:

    我使用与富士类似的方法。唯一的区别是我将ViewModelResourceDictionary 相互独立导出,因此它们仍然是松散耦合的。

    [Export(typeof(IPlugin)]//i use metadata too
    public class Viewmodel1 {}
    
    [Export(typeof(IPlugin)]//i use metadata too
    public class Viewmodel2 {}
    
    [ResourceDictionaryExport]
    public partial class MyResourceDictionary 
    {
        public MyResourceDictionary ()
        {
            InitializeComponent();
        }
    }
    

    在我的插件应用程序中,我添加了所有 ResourceDictionaries

    app.xaml.cs

     [ImportMany("Resourcen", typeof(ResourceDictionary))]
     private IEnumerable<ResourceDictionary> _importResourcen;
    
     foreach (var resourceDictionary in _importResourcen)
     {
         this.Resources.MergedDictionaries.Add(resourceDictionary);
     }
    

    为了完整起见

    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class ResourceDictionaryExportAttribute : ExportAttribute
    {
        public ResourceDictionaryExportAttribute() : base("Resourcen", typeof(ResourceDictionary))
        {
    
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-28
      • 1970-01-01
      相关资源
      最近更新 更多