构建可扩展的应用程序,特别是对于WinForm应用程序是特别有好处的。我们知道,企业的需求是瞬息万变的,企业在使用软件的过程中,很可能对于现有的需求有变动甚至是提出新的需求来,可是我们的软件已经部署在企业的各个客户端中,要想将根据企业新的需求编写的模块集成到现有程序中去,我们必须重新编译整个软件,然后打包再进行重新部署,这无疑有非常大的工作量。怎样才能将新编写的模块集成到现有程序中去,而又不用重新编译整个应用程序?这就是我们接下来要讨论的话题。

利用C# 构建可扩展的应用程序,就是利用.Net的反射机制以及特性编程实现,原理我就不介绍了,很多C#的书籍都说的很清楚,接下来,我将演示如何搭建可扩展的应用程序。

一、首先建立一个类库IplugTypes

该类库定义了插件程序必须遵循的接口Iplug和一个自定义特性,在该类库中导入System.Windows.Forms的引用,代码如下:


 1 using System;
2  using System.Collections.Generic;
3  using System.Linq;
4  using System.Text;
5 using System.Windows.Forms;
6
7 namespace IplugTypes
8 {
9 /// <summary>
10 /// 插件必须继承该接口规范
11 /// </summary>
12 public interface Iplug
13 {
14 void FormShow(Form mainForm);
15 }
16 [AttributeUsage(AttributeTargets.Class)]
17 public sealed class AssemblyInfoAndCompanyInfoAttribute : System.Attribute
18 {
19 /// <summary>
20 /// 程序集名称(不包括扩展名)
21 /// </summary>
22 public string AssemblyName { getset; }
23 /// <summary>
24 /// 程序集版本
25 /// </summary>
26 public string AssemblyVersion { getset; }
27 /// <summary>
28 /// 公司名称
29 /// </summary>
30 public string CompanyName { getset; }
31 /// <summary>
32 /// 菜单名(在承载的主程序中要显示的名称)
33 /// </summary>
34 public string MenuName { getset; }
35 public AssemblyInfoAndCompanyInfoAttribute()
36 {
37
38 }
39
40 }
41 }

二、建立WinForm插件程序

建立一个WinForm窗体程序,引入上面建立的IplugTypes类库的引用,如下图:

在该窗体类中继承IPlug接口,在Iplug接口的FormShow方法中实例化本窗体,同时用AssemblyInfoAndCompanyInfo自定义属性类描述该窗体类,代码如下:


 1 using System.Drawing;
2 using System.Linq;
3 using System.Text;
4 using System.Windows.Forms;
5 using IplugTypes;
6
7 namespace WinFormIPlug
8 {
9 [AssemblyInfoAndCompanyInfo(AssemblyName = "WinFormIPlug",AssemblyVersion="1.0.0.0",CompanyName="DTXY",MenuName="C#插件")]
10 public partial class Form1 : Form,Iplug
11 {
12 public Form1()
13 {
14 InitializeComponent();
15 }
16
17 private void button1_Click(object sender, EventArgs e)
18 {
19 MessageBox.Show("这是一个插件程序!");
20 }
21
22 void Iplug.FormShow(Form mainForm)
23 {
24 Form1 fm = new Form1();
25 fm.MdiParent = mainForm;
26 fm.Show();
27 // throw new NotImplementedException();
28 }
29 }
30 }

将该窗体的输出类型设置为类库,然后进行编译,如下图

三、构建可扩展的应用程序

建立一个名称为WindowsFormMain的窗体应用程序,添加menuStrip控件,并设置该窗体的IsMdiContainer属性为True,指示该窗体为MDI对文档父窗体。如下图:

引入对程序集IplugTypes的引用,导入System.Reflection的命名空间。然后,添加FrmAdd窗体,用于导入插件程序,如下图:

同样在FrmAdd窗体类中引入对程序集IplugTypes的引用,导入System.Reflection的命名空间,该窗体类的代码如下:


  1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Windows.Forms;
9 using IplugTypes;
10 using System.Reflection;
11 using System.IO;
12
13 namespace WindowsFormMain
14 {
15 public partial class FrmAdd : Form
16 {
17 public FrmAdd()
18 {
19 InitializeComponent();
20 }
21 /// <summary>
22 /// 插件加载路径
23 /// </summary>
24 public string Path { getprivate set; }
25
26 private void btnBrowse_Click(object sender, EventArgs e)
27 {
28
29 if (this.openFileDialog1.ShowDialog() == DialogResult.OK)
30 {
31 if (this.openFileDialog1.SafeFileName.Split('.')[1].ToUpper() != "DLL")
32 {
33 MessageBox.Show("您选择的不是动态类型库,扩展名为.DLL!");
34 return;
35 }
36 string strFilePath = this.openFileDialog1.FileName;
37 Path = strFilePath;
38 this.textBox1.Text = Path;
39 if (!LoadAssembly(strFilePath))
40 {
41 MessageBox.Show("插件加载失败,请确认插件是否支持Iplug接口!");
42 }
43 }
44 }
45 private bool LoadAssembly(string strFilePath)
46 {
47 bool isRight = false;
48 try
49 {
50 Assembly asm = Assembly.LoadFrom(strFilePath);
51 var types = from t in asm.GetTypes()
52 where t.IsClass && t.GetInterface("Iplug"!= null
53 select t;
54 if (types.Count() <= 0)
55 {
56 return isRight;
57 }
58 foreach (Type item in types)
59 {
60 DisplayAssemblyInfo(item);
61 }
62 isRight = true;
63 }
64 catch (Exception ex)
65 {
66 MessageBox.Show(ex.Message);
67 return isRight;
68 }
69 return isRight;
70 }
71 private void DisplayAssemblyInfo(Type t)
72 {
73 var asmInfo = from n in t.GetCustomAttributes(false)
74 where n.GetType() == typeof(AssemblyInfoAndCompanyInfoAttribute)
75 select n;
76 foreach (AssemblyInfoAndCompanyInfoAttribute item in asmInfo)
77 {
78 string[] strItems = { t.Assembly.GetName().Name, item.AssemblyVersion, item.CompanyName };
79 ListViewItem lv = new ListViewItem(strItems);
80 this.listView1.Items.Add(lv);
81 }
82 }
83
84 private void btnSub_Click(object sender, EventArgs e)
85 {
86 if (string.IsNullOrEmpty(Path))
87 {
88 MessageBox.Show("没有任何可导入的插件程序!");
89 return;
90 }
91 FileInfo fi = new FileInfo(Path);
92 fi.CopyTo(Application.StartupPath + "\\" + fi.Name, true);
93 DialogResult = DialogResult.OK;
94 }
95
96 private void btnCancel_Click(object sender, EventArgs e)
97 {
98 DialogResult = DialogResult.Cancel;
99 }
100 }
101 }

在主窗体Form1中的MenuScript控件的File->Add-in..菜单中实例话FrmAdd窗体并以模式窗体的方式显示出来,代码如下:


 1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Windows.Forms;
9 using IplugTypes;
10 using System.Reflection;
11
12 namespace WindowsFormMain
13 {
14 public partial class Form1 : Form
15 {
16 public Form1()
17 {
18 InitializeComponent();
19 }
20
21 private void Form1_Load(object sender, EventArgs e)
22 {
23
24 }
25
26 private void addInToolStripMenuItem_Click(object sender, EventArgs e)
27 {
28 FrmAdd fm = new FrmAdd();
29 fm.ShowDialog();
30 }
31 }
32 }

现在,插件已导入到应用程序的启动目录下,那怎样在主窗体的MenuScript菜单中创建一个插件窗体的菜单并且单击该菜单会显示出插件窗体来? 我们接下来继续讨论:

我的设计思路是:维护一个XML文件,该XML文件用于记录主窗体MenuScript的菜单项,主窗体在启动时读取该XML文件,然后动态创建菜单。

当然为了演示,我这里只是手动创建一个名叫:Iplug.xml的文件,并且保存在应用程序的启动目录下。读者完全可以在导入插件时,用程序自动创建。该XML文件的格式如下:

1 <?xml version="1.0" encoding="gb2312"?>
2 <root>
3 <menu name="C#插件" assemblyName="WinFormIPlug">
4 </menu>
5 </root>

当然如果有多个插件,可建立多个<menu>节点,这里<menu>节点的 name属性是该插件要在主程序中显示的菜单名,assemblyName属性是插件程序集的名称(不包括.dll的扩展名)。

建立好该XML文件后,保存到主程序的启动目录下,然后在主窗体启动时读取该XML文件,动态的创建菜单。主窗体的代码如下:


  1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Windows.Forms;
9 using IplugTypes;
10 using System.Reflection;
11 using System.Xml;
12 using System.Xml.Linq;
13 using System.IO;
14
15 namespace WindowsFormMain
16 {
17 public partial class Form1 : Form
18 {
19 public Form1()
20 {
21 InitializeComponent();
22 }
23
24 private void Form1_Load(object sender, EventArgs e)
25 {
26 try
27 {
28 this.AddMenus();
29 }
30 catch (Exception ex)
31 {
32 MessageBox.Show(ex.Message, "提示", MessageBoxButtons.OK);
33 }
34 }
35 /// <summary>
36 /// 加载程序菜单
37 /// </summary>
38 private void AddMenus()
39 {
40 string strFilePath = Application.StartupPath + "\\" + "Iplug.xml";
41 if (!File.Exists(strFilePath))
42 {
43 XmlDocument dom = new XmlDocument();
44 XmlDeclaration declar = dom.CreateXmlDeclaration("1.0""utf-8"null);
45 dom.AppendChild(declar);
46 XmlElement root = dom.CreateElement("root");
47 dom.AppendChild(root);
48 dom.Save(strFilePath);
49 }
50 else
51 {
52 XDocument xd = XDocument.Load(strFilePath);
53
54 var menus = from n in xd.Descendants("menu")
55 select n;
56 if (menus.Count() <= 0)
57 {
58 return;
59 }
60 foreach (var item in menus)
61 {
62 string menuName = item.Attribute("name").Value;
63 ToolStripMenuItem ts = (ToolStripMenuItem)this.menuStrip1.Items.Add(menuName);
64 ts.Tag = item.Attribute("assemblyName").Value;
65 ts.Click += new EventHandler(ts_Click);
66 }
67 }
68 }
69 void ts_Click(object sender, EventArgs e)
70 {
71 try
72 {
73 ToolStripMenuItem tool = (ToolStripMenuItem)sender;
74 string assemblyName = tool.Tag.ToString();
75 Assembly asm = Assembly.Load(assemblyName);
76 var types = from n in asm.GetTypes()
77 where n.IsClass && n.GetInterface("Iplug"!= null
78 select n;
79 if (types.Count() <= 0)
80 {
81 return;
82 }
83 foreach (Type t in types)
84 {
85 Iplug plug = (Iplug)Activator.CreateInstance(t);
86 plug.FormShow(this);
87 }
88 }
89 catch (Exception ex)
90 {
91 MessageBox.Show(ex.Message);
92 }
93 //throw new NotImplementedException();
94 }
95
96 private void addInToolStripMenuItem_Click(object sender, EventArgs e)
97 {
98 FrmAdd fm = new FrmAdd();
99 fm.ShowDialog();
100 }
101 }
102 }

C#构建可扩展的应用程序(插件)的更多相关文章

  1. 构建可扩展的GPU加速应用程序(NVIDIA HPC)

    构建可扩展的GPU加速应用程序(NVIDIA HPC) 研究人员.科学家和开发人员正在通过加速NVIDIA GPU上的高性能计算(HPC)应用来推进科学发展,NVIDIA GPU具有处理当今最具挑战性 ...

  2. Gradle 1.12用户指南翻译——第四十五章. 应用程序插件

    本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  3. 【原创】Odoo开发文档学习之:构建接口扩展(Building Interface Extensions)(边Google翻译边学习)

    构建接口扩展(Building Interface Extensions) 本指南是关于为Odoo的web客户创建模块. 要创建有Odoo的网站,请参见建立网站;要添加业务功能或扩展Odoo的现有业务 ...

  4. [Qt插件]-02创建应用程序插件(插件化开发的一种思路)

    本篇是学习Qt Creator快速入门,插件开发的笔记   分为两部分 创建插件 使用插件的应用程序(测试插件)   插件是被使用的应用程序加载使用的. 是使用插件的应用程序定义接口,插件按照接口来实 ...

  5. NVIDIA DeepStream 5.0构建智能视频分析应用程序

    NVIDIA DeepStream 5.0构建智能视频分析应用程序 无论是要平衡产品分配和优化流量的仓库,工厂流水线检查还是医院管理,要确保员工和护理人员在照顾病人的同时使用个人保护设备(PPE),就 ...

  6. Permit.js – 用于构建多状态原型的 jQuery 插件

    Permit.js 是一个 jQuery 插件,用于构建交互的,多态的网站原型和应用程序原型.也许你的网站有的功能仅适用于登录的成员,只有管理员才能使用或者你的应用程序会根据线上或离线有不同的功能,这 ...

  7. 面向 Java 开发人员的 Ajax: 构建动态的 Java 应用程序

    面向 Java 开发人员的 Ajax: 构建动态的 Java 应用程序 Ajax 为更好的 Web 应用程序铺平了道路 在 Web 应用程序开发中,页面重载循环是最大的一个使用障碍,对于 Java™ ...

  8. Building Modern Web Apps-构建现代的 Web 应用程序

    Building Modern Web Apps-构建现代的 Web 应用程序 视频长度:1 小时左右 视频作者:Scott Hunter 和 Scott Hanselman 视频背景:Visual ...

  9. 如何使用TDD和React Testing Library构建健壮的React应用程序

    如何使用TDD和React Testing Library构建健壮的React应用程序 当我开始学习React时,我努力的一件事就是以一种既有用又直观的方式来测试我的web应用程序. 每次我想测试它时 ...

随机推荐

  1. Mac音频播放

    Mac音频播放 audioqueue播放pcm数据 http://msching.github.io/blog/2014/08/02/audio-in-ios-5/ audiounit播放pcm数据  ...

  2. linux技能四 用户管理

    用户管理:用户类型,添加用户,修改用户,删除用户,查看用户信息,用户的切换,添加组,修改组,删除组,查看组 用户类型:超级用户:root,UID=1 系统用户:运行系统服务的,不能登陆的,UID=(1 ...

  3. Github强制找回管理员账号密码

    步骤: 1. 登录Github所在的服务器,切换用户为git:su git 2. 进入Github的Rails控制台:gitlab-rails console production 3. 查看超级管理 ...

  4. 前端cdn库推荐

    后端编程人员,有时作功能调试时会用到jquery.layer等的前端库文件,用得较多的我们可以下载下来放到自己的电脑上,有些偶尔使用一次的类库插件就没必要全都下载下来,毕竟不用的类库多了,自己找到它都 ...

  5. MySQL存储过程01

    过程:封装了若干条语句,调用时,这些封装体执行 函数:是一个由返回值的’过程‘ 过程是没有返回值的函数 我们把若干条sql封装起来,起个名字---过程 把此过程存储在数据库中------存储过程 存储 ...

  6. 线程中的join方法,与synchronized和wait()和notify()的关系

    什么时候要用join()方法? 1,join方法是Thread类中的方法,主线程执行完start()方法,线程就进入就绪状态,虚拟机最终会执行run方法进入运行状态.此时.主线程跳出start方法往下 ...

  7. python中str和byte的相互转化

    在涉及到网络传输的时候,数据需要从str转换成btye才能进行传输. python byte 转 str , str 转 byte 其实很简单:原理图如下:在这里插入图片描述案例: a: str = ...

  8. django项目中使用bootstrap插件的分页功能。

    官网下载bootstrap插件放到项目中的static文件中 路由 path('blog-fullwidth/', login.fullwidth,name='fullwidth'), 前端页面引入 ...

  9. danci6

    current 英 ['kʌr(ə)nt] 美 ['kɝənt] adj. 现在的:流通的,通用的:最近的:草写的 n. (水,气,电)流:趋势:涌流 n. (Current)人名:(英)柯伦特

  10. docker-compose更新image命令

    docker-compose stop docker-compose up -d --build