首页 > Windows开发 > 详细

WPF中MEF的简单使用

时间:2019-02-20 22:27:03      阅读:219      评论:0      收藏:0      [点我收藏+]

在用WPF MVVMLight做毕设的过程中,偶然看到关于MEF插件式开发的技术文章,就想试试看能不能把每个模块做成插件。

****
我原先想实现的功能就是一个模块就是一个插件,所有插件加载到主界面的TreeView导航中,点击对应的项,显示对应的页面,而TreeView的集合并非我手动一个一个new TreeViewItem 是根据每个页面的元数据来生成

后来我也确实部分实现了这个小需求,但是不能做成无限极树,而且在用CustomExportMetadata设置元数据时也非常不合理,只能删掉然后一个一个new TreeViewItem了 (╯‵□′)╯︵┻━┻(菜鸡的无能狂怒)
****

先来看看简陋的效果图(啥也看不出来)

技术分享图片

然后看下项目结构

技术分享图片

  • Core 核心接口库,想要让我加载你的插件,你就必须遵守我的规则
  • Plugins 用户控件库,插件
  • MvvmLight.MEF 主程序加载插件

****MEF需要用到的dll****

  • System.ComponentModel.Composition
  • System.ComponentModel.Composition.Hosting

****注意****

插件没加载进去先看看查找插件dll的路径对不对

  1. 在Core类库中定义一个视图IView接口

     public interface IView
     {
         //约束插件类型
                     //我并不关心你的class叫什么名字,我只需要知道谁继承了这个接口,然后我就可以转换成IView类型的。
                     //这难道就是传说中的面向接口编程??
     }
  2. 定义一个元数据IMetaData接口 (根据你的需要定义元数据)

         /// <summary>
         /// 优先级
         /// </summary>
         [DefaultValue(0)]
         int Priority { get; }
         /// <summary>
         /// 名称(不能重复)
         /// </summary>
         string Name { get; }
         /// <summary>
         /// 描述
         /// </summary>
         string Description { get; }
         /// <summary>
         /// 作者
         /// </summary>
         string Author { get; }
         /// <summary>
         /// 版本
         /// </summary>
         string Version { get; }
  3. 实现元数据接口的特性

     [MetadataAttribute]
     [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
     public class CustomExportMetadata : ExportAttribute, IMetaData
     {
         /// <summary>
         /// 优先级
         /// </summary>
         public int Priority { get; private set; }
         /// <summary>
         /// 名称
         /// </summary>
         public string Name { get; private set; }
         /// <summary>
         /// 描述
         /// </summary>
         public string Description { get; private set; }
         /// <summary>
         /// 作者
         /// </summary>
         public string Author { get; private set; }
         /// <summary>
         /// 版本
         /// </summary>
         public string Version { get; private set; }
    
         /// <summary>
         /// 构造函数
         /// </summary>
         public CustomExportMetadata() : base(typeof(IMetaData))
         {
             this.UId = Guid.NewGuid().ToString();
         }
    
         /// <summary>
         /// 构造函数
         /// </summary>
         public CustomExportMetadata() : base(typeof(IMetaData))
         {
    
         }
    
         /// <summary>
         /// 构造重载
         /// </summary>
         /// <param name="priority">优先级</param>
         public CustomExportMetadata(int priority) : this()
         {
             this.Priority = priority;
         }
    
         // <summary>
         /// 构造重载
         /// </summary>
         /// <param name="priority">优先级</param>
         /// <param name="name">名称</param>
         public CustomExportMetadata(int priority, string name) : this(priority)
         {
             this.Name = name;
         }
    
         /// <summary>
         /// 构造重载
         /// </summary>
         /// <param name="priority">优先级</param>
         /// <param name="name">名称</param>
         /// <param name="description">描述</param>
         public CustomExportMetadata(int priority, string name,string description) : this(priority, name)
         {
             this.Description = description;
         }
    
         /// <summary>
         /// 构造重载
         /// </summary>
         /// <param name="priority">优先级</param>
         /// <param name="name">名称</param>
         /// <param name="description">描述</param>
         /// <param name="author">作者</param>
         public CustomExportMetadata(int priority, string name,string description,string author) : this(priority, name, description)
         {
             this.Author = author;
         }
    
         /// <summary>
         /// 构造重载
         /// </summary>
         /// <param name="priority">优先级</param>
         /// <param name="name">名称</param>
         /// <param name="description">描述</param>
         /// <param name="author">作者</param>
         /// <param name="version">版本</param>
         public CustomExportMetadata(int priority, string name,string description,string author,string version) : this(priority, name, description, author)
         {
             this.Version = version;
         }
  4. 在你想被加载的UserControl.xaml.cs里继承IView并添加Export和CustomExportMetadata特性

    Export的参数是声明你要把这个类导出为什么类型

    CustomExportMetadata的参数对照上面的重载

    我们知道MEF作为IOC的方式之一,它的主要作用是解耦,MEF加上面向接口编程,可以使得你的设计更加灵活。我们知道类的构造函数是可以重载的,我们通过构造函数可以向对象传递参数。那么如果我们的MEF也需要通过构造函数传参怎么办呢?别担心,有我们神奇的ImportingConstructor为您解决.
    [https://www.cnblogs.com/landeanfen/p/4848885.html](https://www.cnblogs.com/landeanfen/p/4848885.html)

     /// <summary>
     /// TestPluginsUserControl.xaml 的交互逻辑
     /// </summary>
     [Export(typeof(IView))]
     [CustomExportMetadata(1, "测试模块1下的第一个页面", "测试模块1下的第一个页面", "HFMS","0.0.1")]
     public partial class TestPluginsUserControl : UserControl, IView
     {
         [ImportingConstructor]
         public TestPluginsUserControl()
         {
             InitializeComponent();
         }
     }
  5. 定义TreeItem类

     public class TreeItem
     {
         public int Id { get; set; }
         public int ParentId { get; set; }
         public string Name { get; set; }
         public List<TreeItem> Nodes { get; set; }
     }
  6. 在主窗体的ViewModel里加载插件(延迟加载和普通加载)这里用的是懒加载(延迟加载) 普通加载请点击最下方链接

     private List<TreeItem> _tvModel;
     /// <summary>
     /// TreeView的ItemSource
     /// </summary>
     public List<TreeItem> TVModel
     {
         get { return _tvModel; }
         set { Set(ref _tvModel, value); }
     }
    
     private ObservableCollection<TabItem> _tabItemTests = new ObservableCollection<TabItem>();
     /// <summary>
     /// TabControl的ItemSource
     /// </summary>
     public ObservableCollection<TabItem> TabItemTests
     {
         get { return _tabItemTests; }
         set { Set(ref _tabItemTests, value); }
     }
    
     //你要导入的类型和元数据数组,所有继承IView接口并导出的页面都在这个数组里
     //后面参数也不知道有啥用,还在学习中
     [ImportMany(typeof(IView),AllowRecomposition = true)]
     private Lazy<IView, IMetaData>[] Plugins { get; set; }
    
     private CompositionContainer container = null;
    
     //窗体加载时调用的方法
     private void ExcuteLoadWindow()
     {
         //获取工作目录
         var dir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
         if (dir.Exists)
         {
             //就是这里,读取所有符合条件的dll
             var catalog = new DirectoryCatalog(dir.FullName, "MEF.*.dll");
             container = new CompositionContainer(catalog);
             try
             {
                 //从特性化对象的数组创建可组合部件,并在指定的组合容器中组合这些部件。
                 container.ComposeParts(this);
             }
             catch (CompositionException ce)
             {
                 Console.Write(ce.Message);
             }
    
             //排个序,我寻思着是不是有的插件还依赖着别的插件,所以需要排序
             Plugins.OrderBy(p => p.Metadata.Priority);
    
             //传个最顶级的节点id和创建好的节点集合
             TVModel = GetTrees(0,SetTrees());
         }
     }
    
     /// <summary>
     /// 递归生成树结构
     /// </summary>
     /// <param name="parentid">父节点Id</param>
     /// <param name="nodes">节点集合</param>
     /// <returns></returns>
     public List<TreeItem> GetTrees(int parentid, List<TreeItem> nodes)
     {
         List<TreeItem> mainNodes = nodes.Where(x => x.ParentId == parentid).ToList<TreeItem>();
         List<TreeItem> otherNodes = nodes.Where(x => x.ParentId != parentid).ToList<TreeItem>();
         foreach (TreeItem dpt in mainNodes)
         {
             dpt.Nodes = GetTrees(dpt.Id, otherNodes);
         }
         return mainNodes;
     }
    
     public List<TreeItem> SetTrees()
     {
         //这里创建节点集合
         List<TreeItem> treeItems = new List<TreeItem>
         {
             //添加模块1
             new TreeItem() { Id = 1, ParentId = 0, Name = "测试模块1", Nodes = new List<TreeItem>() },
             new TreeItem() { Id = 2, ParentId = 1, Name = "测试模块1下的第一个页面", Nodes = new List<TreeItem>() },
             new TreeItem() { Id = 3, ParentId = 1, Name = "测试模块1下的第二个页面", Nodes = new List<TreeItem>() },
             //添加模块1下更多级节点
             new TreeItem() { Id = 4, ParentId = 1, Name = "有子节点的子节点", Nodes = new List<TreeItem>() },
             new TreeItem() { Id = 5, ParentId = 4, Name = "测试模块1下的有子节点的子节点第一个页面", Nodes = new List<TreeItem>() },
             //添加模块2
             new TreeItem() { Id = 6, ParentId = 0, Name = "测试模块2", Nodes = new List<TreeItem>() },
             new TreeItem() { Id = 7, ParentId = 6, Name = "测试模块2下的第一个页面", Nodes = new List<TreeItem>() },
             new TreeItem() { Id = 8, ParentId = 6, Name = "测试模块2下的第二个页面", Nodes = new List<TreeItem>() }
         };
         return treeItems;
     }
    
     private void ExcuteLoadUserControl(object obj)
     {
         if (obj is TreeView treeview)
         {
             foreach (var item in Plugins)
             {
                 if (item.Metadata.Name == (treeview.SelectedItem as TreeItem).Name)
                 {
                     if (TabItemTests.Where(x => x.Header.ToString() == item.Metadata.Name).Count() < 1)
                     {
                         TabItemTests.Add(new TabItem()
                         {
                             Header = item.Metadata.Name,
                             Content = item.Value,
                         });
                     }                       
                     return;
                 }
             }
         }
     }

最后有个关于MVVMLight的问题没解决

UserControl的ViewModel我也是统一用ViewModelLocator来管理,
但是在xaml里 ViewModel不能绑定到UserControl的DataContext,只能绑给次一级元素的DataContext

    <UserControl.Resources>
        <ResourceDictionary>
            <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid DataContext="{Binding Main,Source={StaticResource Locator}}">
        <TextBlock Text="{Binding TestText}"/>
    </Grid>

参考链接:

https://www.cnblogs.com/hippieZhou/p/9404043.html#4183303

感谢@hippieZhou园友的指导。
第一次写随笔,只是根据这个链接照打了一遍。:)

WPF中MEF的简单使用

原文:https://www.cnblogs.com/hfms/p/10409403.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!