C#構建可擴展的應用程序(插件)


構建可擴展的應用程序,特別是對於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 }
復制代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM