若干年前,老周写了几篇有关MEF的烂文,简单地说,MEF是一种动态扩展技术,比如可以指定以某个程序集或某个目录为搜索范围,应用程序在运行时会自动搜索符合条件的类型,并自动完成导入,这样做的好处是,主程序的代码不用改来改去,只需要把扩展的程序集放到对应的目录下就可以了。

MEF不仅可以用于“看不见”的类型扩展上,对于“看得见”的类型照样适用,比如窗口、控件之属,你要是够牛逼的话,甚至可以把它用到ASP.NET上,不过这个玩意儿估计要配合重写路由规则才能实现,根据URL传的参数来跳转到具体的页面。

较为简单的,像Windows Forms中的窗口,WPF中的窗口或控件,就可以直接运用MEF来完成扩展,主应用程序界面可以动态生成菜单项或按钮来打开窗口就可以了。而各个窗口的实现代码可以写在一个类库项目中。

下面,咱们用一个实实在在的例子来说明一下。

新建一个类库项目,然后在里面做三个WPF窗口,XAML文档如何与代码类关联,这个不要问我,问MSDN姐姐去。

因为这是做测试,窗口的UI布局你可以随便设计。

给大家一个提示吧,XAML文件和窗口类的代码文件的关联方法,和ASP.NET中.aspx文件与代码文件的关联方法一样。例如XAML文件名叫 test.xaml,那么对应的代码文件名就是test.xaml.cs(VB语言的话,是test.xaml.vb)。

对窗口来说,一般是从Window类派生,所以,XAML文档的根元素要写Window,比如

<Window>
……
</Window>

XAML中有两个必备的命名空间要引入:

<Window x:Class="wpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" …………

.../xaml/presentation 表示的WPF中的UI类型,比如Button、Canvas等;而那个带x前缀的.../xaml表示的是XAML语法本身特有的东西,比如x:Class,这个特性就是联合XAML文件和代码文件的关键,用它来指定窗口类的名字,类名要包括命名空间名。

下面的步骤相当重要,不然就无法MEF了。

打开窗口的代码文件,在窗口类声明上添加导出声明。如下

    [Export(typeof(Window))]
[ExportMetadata("name", "窗口 A")]
public partial class DemoWindow1 : Window
{
public DemoWindow1()
{
InitializeComponent();
Title = $"演示窗体 -- {nameof(DemoWindow1)}";
}
}

声明导出需要一个协定,因为类型是可以动态扩展的,所以这些扩展的类型必须要向运行时表明它们有一个共同点,以便让MEF能够找到它,这就是类型协定。我们知道,所有窗口类都有一个共同点——从Window类派生,故而在声明ExportAttribute时,用Window类的Type来标注协定。

ExportMetadataAttribute表示的是元数据,它是可选的,指定方式和字典的key - value形式差不多,name是字符串,value是Object类型,虽然可以指任何类型的value,但最好是可序列化的类型或者基础类型(byte,string,int等),这样方便传递。在接收扩展的代码中,可以用IDictionary<string, object>类型来接收元数据,也可以自定义一个类型(接口、类)来接收,只要属性/字段的名字和ExportMetadataAttribute中的name相等就行了,这样元数据就会自动填充到类型的属性/字段成员中。

比如,如果你指定元数据的name为“Age”,value为25,那么你自定义的类型只要公开一个名为Age的属性或字段即可,获取时会自动填充数据。

这里我一口气做了三个窗口,最后,可以定义一个类,把上面的N个窗口批量导入这个类的一个属性中,随后导出这个类的这个属性。

    class WindowsCompos
{
[Export("extWindows")]
[ImportMany(typeof(Window))]
public IEnumerable<ExportFactory<Window, IDictionary<string,object>>> ExtWindows { get; set; }
}

ImportMany可以一次性导入多个类型,因为扩展的窗口有N个,所以要使用这个特性来批量导入,还记得吧,前面的窗口都是以Window的Type作为协定来导出的,所以在导入时,一定指定匹配的协定,不然无法导入。

因为类型有多个,所以要用IEnumerable<T>(协变)来存放,而其中的T为ExportFactory<T, TMetadata>,本来用ExportFactory<T>就可以了,但由于我为每个窗口的导出定义了元数据,所以要使用支持获取元数据的工厂类型。

这个类可以不定义为public,因为导出的是它的属性,而且对于MEF来说,非public的成员都可以导出,只要你指定导出协定即可。

对于ExtWindows属性,导出声明就不必使用Type作为协定了,直接指定一个名字来做协定就可以了,本例是extWindows,注意这个协定名是区分大小写的,ext和Ext被视为不同的协定。

通常,接收扩展类型用的是Lazy<T>,以达到延迟实例化,但是,这个项目比较特殊,不能用Lazy来承载类型。WPF的窗口类有个特点,就是每次显示窗口必须使用新的实例,因为窗口一旦Close之后,就不能再次Show了,只能重新new一个实例才能Show。基于这原因,用ExportFactory类最好,这个类每次访问都能重新创建实例,调用CreateExport方法能创建一个ExportLifetimeContext<T>实例,再通过这个ExportLifetimeContext<T>实例的Value属性来得到窗口实例。

ExportLifetimeContext<T>实现了IDisposable接口,可以写在using语句中,用完后释放掉。

现在回到主应用程序项目,开始导入扩展窗口。

主窗口用一个菜单就行了,每个导入的窗口类型将作为菜单项。

    <Grid>
<Menu VerticalAlignment="Top">
<MenuItem Header="窗口" Name="menuWindows">
<!-- ****** -->
</MenuItem>
</Menu>
</Grid>

下面代码将获取导出对象,由于刚才用IEnumable<T>来导入了窗口类型,所以此处只需要获取这个属性的值即可。

        IEnumerable<ExportFactory<Window, IDictionary<string, object>>> ext_windowslist;
CompositionContainer container = null;
public MainWindow()
{
InitializeComponent(); Assembly extAss = Assembly.Load(nameof(ExtWindowLib));
AssemblyCatalog catelog = new AssemblyCatalog(extAss); container = new CompositionContainer(catelog); CompositionExtWindows();
AddExtToMenuitems(); menuWindows.AddHandler(MenuItem.ClickEvent, new RoutedEventHandler(OnMenuItemClicked));
}

CompositionContainer是个容器,用它可以组合所有获取到的扩展类型,实例化容器时,要指定一个搜索范围,这里我指定它从刚才那个类库项目中搜索。因为我已经引用了这个类库项目,所以调用Assembly.Load(程序集名)就可以直接加载了。

CompositionExtWindows方法负责从容器中获取导出的IEnumrable<T>对象,代码如下:

        private void CompositionExtWindows()
{
if (container == null) return; ext_windowslist = container.GetExportedValue<IEnumerable<ExportFactory<Window, IDictionary<string, object>>>>("extWindows");
}

直接调用GetExportedValue方法就可以获取到导出的属性值,参数是刚刚给ExtWindows属性指定的协定名。

AddExtToMenuitems方法把获取到的扩展窗口类型添加到子菜单项,这样一来,有多少个扩展窗口,就有多少个菜单项。

        private void AddExtToMenuitems()
{
foreach (var factory in ext_windowslist)
{
// 元数据
IDictionary<string, object> metadata = factory.Metadata;
string hd = metadata["name"] as string;
MenuItem mnitem = new MenuItem();
mnitem.Header = hd;
mnitem.Tag = factory;
menuWindows.Items.Add(mnitem);
}
}

让菜单项的Tag属性引用 ExportFactory实例,以便在Click事件处理方法中访问。

菜单项的Click事件处理如下:

        private void OnMenuItemClicked(object sender, RoutedEventArgs e)
{
MenuItem item = e.Source as MenuItem;
ExportFactory<Window> fact = item.Tag as ExportFactory<Window>;
if (fact != null)
{
using (var lifeobj = fact.CreateExport())
{
Window w = lifeobj.Value;
w.Show();
}
}
}

从Value属性中获取窗口实例,就可以调用Show方法来显示窗口了。

来,运行一下,看看如何。运行后,会自动添加三个菜单项,因为我刚刚做了三个窗口。

点击对应的菜单,就能打开对应窗口。

现在,不妨往类库项目中再添加一个窗口。

    [Export(typeof(Window))]
[ExportMetadata("name", "窗口 D")]
public partial class DemoWindow4 : Window
{
public DemoWindow4()
{
InitializeComponent();
Title = $"演示窗体 -- {nameof(DemoWindow4)}";
}
}

主应用程序的代码不用做任何改动,然后直接运行。

此时,你会看到,第4个窗口也自动加进来了。

有没有发现,这几个菜单项的排序好像不太好看,要是能按一定顺序排列多好。这个实现起来不难,老周就不实现了,你自己试着干吧。

老周可以给个提示,还记得在ExportAttribute声明导出类型时,可以指定元数据,例子中,老周指定了一个叫name的元数据,你可以指定一个叫order的元数据,值为数值,比如第一个窗口为1,第二个窗口为2……

然后,在主程序项目中获取组合扩展时,可以用IEnumerable<T>的扩展方法进行排序,也可以用LinQ语法来排序。

好了,文章就写到这里吧,See you.

示例代码下载

===================================================================

有热心朋友给老周留言,问老周,为什么你的博文的右下角,老有人点“反对”,老周你是不是得罪人了。

谢谢朋友,你不说我还真没注意,因为老周从来不在意那些虚的东西,故一直没注意到这个。实话说,老周从来不得罪人,老周只会得罪妖魔鬼怪,所以朋友多虑了。

至于说右下角那两个按钮,可能是一些没文化的人,本来是想点击左边的,由于不认识汉字,错点了右边的按钮。

总之,大家不要在意这些无关紧要的东西,如果你觉得老周写的烂文对你有用,那你就姑且当娱乐新闻看看吧,毕竟老周的写作水平不高,老周已经在努力优化了,争取多读点经典名著和大师著作,提升水平。

【WPF】运用MEF实现窗口的动态扩展的更多相关文章

  1. [WPF疑难] 继承自定义窗口

    原文 [WPF疑难] 继承自定义窗口 [WPF疑难] 继承自定义窗口 周银辉 项目中有不少的弹出窗口,按照美工的设计其外边框(包括最大化,最小化,关闭等按钮)自然不同于Window自身的,但每个弹出框 ...

  2. [WPF疑难]如何禁用窗口上的关闭按钮

    原文 [WPF疑难]如何禁用窗口上的关闭按钮 [WPF疑难]如何禁用窗口上的关闭按钮                                           周银辉 哈哈,主要是调用Rem ...

  3. WPF中静态引用资源与动态引用资源的区别

    WPF中静态引用资源与动态引用资源的区别   WPF中引用资源分为静态引用与动态引用,两者的区别在哪里呢?我们通过一个小的例子来理解. 点击“Update”按钮,第2个按钮的文字会变成“更上一层楼”, ...

  4. php下安装动态扩展库的相关事项

    php下安装动态扩展库的相关事项 我下载的Apache版本为2.4,PHP版本为7.0. 将Apache与PHP集成配置好后(PHP安装目录为:G:\computer\web\php7,apache安 ...

  5. 【.net 深呼吸】WPF 中的父子窗口

    与 WinForm 不同,WPF 并没有 MDI 窗口,但 WPF 的窗口之间是可以存在“父子”关系的. 我们会发现,Window 类公开了一个属性叫 Owner,这个属性是可读可写的,从名字上我们也 ...

  6. 【转载】docker 应用之动态扩展容器空间大小

    docker 容器默认的空间是 10G, 如果想指定默认容器的大小(在启动容器的时候指定),可以在 docker 配置文件里通过 dm.basesize 参数指定,比如 docker -d --sto ...

  7. WPF编程,通过Double Animation动态旋转控件的一种方法。

    原文:WPF编程,通过Double Animation动态旋转控件的一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/art ...

  8. WPF编程,通过Double Animation动态更改控件属性的一种方法。

    原文:WPF编程,通过Double Animation动态更改控件属性的一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/a ...

  9. WPF编程,通过Double Animation动态缩放控件的一种方法。

    原文:WPF编程,通过Double Animation动态缩放控件的一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/art ...

随机推荐

  1. Entity Framework Core 实现MySQL 的TimeStamp/RowVersion 并发控制

    将通用的序列号生成器库 从SQL Server迁移到Mysql 遇到的一个问题,就是TimeStamp/RowVersion并发控制类型在非Microsoft SQL Server数据库中的实现.SQ ...

  2. 浅谈WEB页面提速(前端向)

    记得面试现在这份工作的时候,一位领导语重心长地谈道——当今的世界是互联网的世界,IT企业之间的竞争是很激烈的,如果一个网页的加载和显示速度,相比别人的站点页面有那么0.1秒的提升,那也是很大的一个成就 ...

  3. Atitit 项目语言的选择 java c#.net  php??

    Atitit 项目语言的选择 java c#.net  php?? 1.1. 编程语言与技术,应该使用开放式的目前流行的语言趋势1 1.2. 从个人职业生涯考虑,java优先1 1.3. 从项目实际来 ...

  4. EntityFramework Core 1.1是如何创建DbContext实例的呢?

    前言 上一篇我们简单讲述了在EF Core1.1中如何进行迁移,本文我们来讲讲EF Core1.1中那些不为人知的事,细抠细节,从我做起. 显式创建DbContext实例 通过带OnConfiguri ...

  5. 使用SecureCRT连接虚拟机(ubuntu)配置记录

    这种配置方法,可以非常方便的操作虚拟机里的Linux系统,且让VMware在后台运行,因为有时候我直接在虚拟机里操作会稍微卡顿,或者切换速度不理想,使用该方法亲测本机效果确实ok,特此记录. Secu ...

  6. PayPal高级工程总监:读完这100篇论文 就能成大数据高手(附论文下载)

    100 open source Big Data architecture papers for data professionals. 读完这100篇论文 就能成大数据高手 作者 白宁超 2016年 ...

  7. 基于改进人工蜂群算法的K均值聚类算法(附MATLAB版源代码)

    其实一直以来也没有准备在园子里发这样的文章,相对来说,算法改进放在园子里还是会稍稍显得格格不入.但是最近邮箱收到的几封邮件让我觉得有必要通过我的博客把过去做过的东西分享出去更给更多需要的人.从论文刊登 ...

  8. BPM任务管理解决方案分享

    一.方案概述任务是企业管理者很多意志的直接体现,对于非常规性事务较多的企业,经常存在各类公司下达的各种任务跟进难.监控难等问题,任务不是完成效果不理解,就是时间超期,甚至很多公司管理层下达的任务都不了 ...

  9. Android之DOM解析XML

    一.DOM解析方法介绍 DOM是基于树形结构的节点或信息片段的集合,允许开发人员使用DOM API遍历XML树,检索所需数据.分析该结构通常需要加载整个文档和构造树形结构,然后才可以检索和更新节点信息 ...

  10. 使用Nginx反向代理 让IIS和Tomcat等多个站点一起飞

    使用Nginx 让IIS和Tomcat等多个站点一起飞 前言: 养成一个好习惯,解决一个什么问题之后就记下来,毕竟“好记性不如烂笔头”. 这样也能帮助更多的人 不是吗? 最近闲着没事儿瞎搞,自己在写一 ...