构建可扩展的应用程序,特别是对于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. SpringBoot 传入JSON对象参数

    1.请求参数格式必须是正确的JSON. 2.在入参中使用注解@RequestBody,用于接收JSON参数,使其自动转对象 3.关于lombok在此产生的一点小坑,@Builder对@RequestB ...

  2. linux查看log软件

    可以使用LNAV软件查看log,还是比较方便的 安装步骤 $ sudo apt install lnav 获取帮助信息 $ lnav -h 查看日志 $ lnav 查看指定日志(后面加上绝对路径) $ ...

  3. Android为TV端助力之反射基本知识

  4. Android数据库GreenDao配置版本问题

    感谢该贴解决我多天的困惑:https://blog.csdn.net/u013472738/article/details/72895747 主要是降低了GreenDao版本 网上很多教程说的版本都是 ...

  5. js跳出循环的方法区别(break,continue,return)(转载)

    转自:http://blog.csdn.net/fxss5201/article/details/52980138 js编程语法之break语句: break语句会使运行的程序立刻退出包含在最内层的循 ...

  6. 【python】udp 数据的发送和接收

    import socket def send_message(): # 创建一个udp套接字 udp_socker = socket.socket(socket.AF_INET,socket.SOCK ...

  7. vue父组件触发子组件方法

    比如应用场景是弹窗中的组件,想要点弹窗时更新该组件展示对应记录的的值 methods: { edit (record) { this.mdl = Object.assign({}, record) t ...

  8. CodeCombat第一关:KITHGARD地牢之KITHGARD精通

    https://www.cnblogs.com/OctoptusLian/p/7397602.html https://www.jianshu.com/p/065581a84879 https://w ...

  9. keepalived+nginx+lnmp 网站架构

    <网站架构演变技术研究> 项目实施手册 2019年8月2日 第一章:  实验环境确认 4 1.1-1.系统版本 4 1.1-2.内核参数 4 1.1-3.主机网络参数设置 4 1-1-4 ...

  10. 动态管理upsteam---nginx_http_dyups_module

    nginx_http_dyups_module  nginx_http_dyups_module是第三方开源软件,它提供API动态修改upstream的配置,并且支持Nginx的ip_hash.kee ...