2011年12月9日
(一)開篇
說起插件(plug-in)式的應用法度大師應當不陌生吧,記得很早以前有一款很風行的MP3播放軟件winmap,它是我記憶裏最早熟悉的一款應用插件模式的應用法度,你可以應用他的插件經管器插入很多的音樂結果器,皮膚,甚至是歌詞顯示的面板。接下來看到了Photoshop應用插件模式經管慮鏡。最後發明只如果大一點的應用法度根蒂根基都應用了插件式的法度框架,就拿我們最常用的對象來說吧,Visual Studio,Office,Delphi,Eclipse等等。Eclipse將插件模式闡揚到了及至,因爲他是開源的的,所以浩繁的愛好者,開闢出了讓人應接不暇的插件。
爲什麼應用插件式的應用法度框架呢?我的答案就是他爲應用法度的功能擴大供給的無窮的想象空間。一個應用法度,無論你前期做了幾許的市場查詢拜訪,需求解析做的多麼完美,你也只是逢迎一項目組人的期望,更甚,你只逢迎了一項目組人的一項目組期望,或者一項目組人在某一時候的一項目組期望。所以當法度公佈今後,你依然有機會供給新的功能而不必從頭公佈法度,人們也可以按照本身的須要來開闢新的功能來滿足本身的需求,據我所知有很多的軟件公司就是專門開闢插件來賺錢,真是一舉多得,何樂而不爲呢?
我們來看一些常見的供給插件模式的應用法度是如何實現插件功能的。據我的應用經驗來看,Visual Studio和Office其實都是主動化法度,經由過程COM的體式格式供給了一組接口。開闢人員可以哄騙這些接口來開闢基於COM的插件,當插件開闢完成後,註冊COM組件。在Visual Studio中你可以應用Add-in經管器來選擇是否啓用插件,而Office似乎省去了這一步,一旦你註冊了Office插件,Office應用法度在啓動的時辰會主動加載插件。COM體式格式似乎最受微軟的寵愛,因爲COM是一種二進制重用標準,用戶可以應用大項目組風行的說話來開闢插件。當然你也可以應用此外體式格式,比如通俗DLL,只是如許對於開闢人員來說實用面就窄了,因爲各個廠商DLL的內部佈局是不盡雷同的,比VC開闢出的DLL和Borland
C++builder開闢出的DLL佈局就不合,須要專門的對象進行轉換。如今,還有別的一種體式格式,應用dotNet的Assembly,應用dotNet的益處是開闢簡單,應用也同樣簡單(不須要註冊),並且你也可用經由過程COM互操縱讓開闢人員可以應用各類說話進行插件開闢,當然用dotNet開闢還是最簡單的,省去不少中心過程。
其實上方介紹的三種體式格式開闢的插件終極還是寄宿在DLL中,從中我們就可以看出一些端倪。爲什麼應用DLL呢?DLL固然也是PE格局,然則他是不克不及自力運行的,一般景象下,都是在運行時加載到應用法度的內存空間。插件模式正好是哄騙了這一點,插件不是應用法度的一項目組,他以二進制的體式格式自力存在,由用戶決意是否應用他。
那麼插件是如何與應用法度進行交互的呢?起首必須有一個契約,應用法度要聲明我有哪些功能是可以被插件應用的,並且具備什麼前提才幹成爲我的插件。其次,應用法度不依附於插件,也就是說,沒有你插件,我也可以很好的運行。再次,應用法度必須有一種策略來獲取插件存在的地位,比如Visual studio是經由過程註冊表的體式格式。最後,應用法度可以經由過程某種體式格式動態的加載插件。
(二)訂立契約
無論是用COM的體式格式,還是通俗DLL,抑或.NET體式格式來實現插件框架,起首要面對的題目就是如何訂立契約。如同我上一篇文章講到的一樣,契約是應用法度和插件之間進行交互的根據和憑證。應用法度必須聲明我有什麼樣的功能可被插件應用,並且插件必須合適什麼前提才幹被我應用。反之,插件必必要知道應用法度供給什麼樣的功能,我才幹將本身的功能融入到應用法度的體系中。本系列文章首要講如何應用.NET實現插件式的應用法度框架,所以其它的體式格式我就不再提了。
如何應用.NET訂立契約呢?起首想到的Interface,其次是抽象類,然則在插件模式中我應用接口,因爲我們是在滿足應用法度的首要目標的根蒂根基上來供給附加的插件功能,就這一點來說,接口更靈活,更輕易擴大。接下來,如何訂立契約的內容呢?這就要按照你的營業需求了,爲了講解的便利,我們定義一個最最根蒂根基的插件式應用法度的插件契約。我們做一個假定,我們的應用法度是一個多文檔的應用法度,包含一個主菜單欄,一個對象欄,菜單欄可以在法度的高低閣下四個標的目標停靠,別的還有一個狀況欄。到後邊,若是有須要,我會擴大這個應用法度,讓他本身供給更多的可供插件應用的功能。所以就今朝而言,我想實現的功能就是讓插件爲主法度添加對象條,菜單項並實現一些簡單的功能。
應用法度向插件供給辦事有兩種體式格式,一種是直接再應用法度接口中聲明屬性或者辦法,一種是將應用法度接口聲明成一個辦事容器。我籌算兩種體式格式都用,明白的功能就在接口中直接聲明成屬性或者辦法,別的將應用法度聲明成一個辦事容器,以便利插入更多的辦事功能,進步應用法度的可擴大性。
下邊是一個很是簡單的應用法度接口定義,對於我們的假定已經足夠了。
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel.Design;
using System.Windows.Forms;
namespace PluginFramework
{
public interface IApplication:IServiceContainer
{
ToolStripPanel LeftToolPanel { get;}
ToolStripPanel RightToolPanel { get;}
ToolStripPanel TopToolPanel { get;}
ToolStripPanel BottomToolPanel { get;}
MenuStrip MainMenuStrip { get;}
StatusStrip StatusBar { get;}
}
}
插件的接口定義:
using System;
using System.Collections.Generic;
using System.Text;
namespace PluginFramework
{
public interface IPlugin
{
IApplication Application { get;set;}
String Name { get;set;}
String Description { get;set;}
void Load();
void UnLoad();
event EventHandler<EventArgs> Loading;
}
}
(三)動態加載
不管你採取什麼體式格式實現插件式的應用法度框架,核心還是動態加載,換句話說,沒有動態加載技巧也就無所謂插件式的應用法度框架了。應用Com實現的話,你可以哄騙Com的API經由過程ProgID來動態創建COM對象,若是應用通俗DLL,你須要應用Windows 的API函數LoadLibrary來動態加載DLL,並用GetProcAddress函數來獲取函數的地址。而應用.NET技巧的話,你須要應用Assembly類的幾個靜態的Load(Load,LoadFile,LoadFrom)辦法來動態加載彙集。
一個Assembly裏可以包含多個類型,由此可知,一個Assembly裏也可以包含多個插件,就像前一篇文章所講,只要它從IPlugin接口派生出來的類型,我們就承認它是一個插件類型。那麼Assembly被動態加載了今後,我們如何獲取Assembly裏包含的插件實例呢?這就要用到反射(Reflection)機制了。我們須要應用Assembly的GetTypes靜態辦法來獲得Assembly裏所包含的所有的類型,然後遍歷所有的類型並斷定每一個類型是不是從IPlugin接口派生出來的,若是是,我們就應用Activator的靜態辦法CreateInstance辦法來獲得這個插件的實例。.NET的動態加載就是這幾個步調。下來,我做一個簡單的例子來練習訓練一下動態加載。起首聲明一點,這個例子很是簡單,純粹是爲了練習訓練動態加載,我們的真正的插件式的應用法度框架裏會有專門的PluginService來負責插件的加載,卸載。
我們的插件位於一個DLL裏,所以我們起首創建一個Class library工程。創建一個FirstPlugin類讓它派生於IPlugin接口,並實現接口的辦法和屬性,因爲本文的目標是演示動態加載,所以IPlugin接口的Loading事務我們就不供給默認的實現了,固然編譯的時辰會給出一個警告,我們不必理會它。這個插件的功能就是在應用法度裏創建一個停靠在主窗體底部的ToolStrip,這個ToolStrip上有一個按鈕,點擊這個按鈕,會彈出一個MessageBox顯示“The first plugin”。下面是代碼:
using System;
using System.Collections.Generic;
using System.Text;
using PluginFramework;
using System.Windows.Forms;
namespace FirstPlugin
{
public class FirstPlugin:IPlugin
{
private IApplication application = null;
private String name="";
private String description = "";
#region IPlugin Members
public IApplication Application
{
get
{
return application;
}
set
{
application = value;
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public string Description
{
get
{
return description;
}
set
{
description = value;
}
}
public void Load()
{
if (application != null && application.BottomToolPanel != null)
{
//創建一個向主法度添加的ToolStrip
ToolStrip sampleToolStrip = new ToolStrip();
ToolStripButton button = new ToolStripButton("Click Me");
button.Click += new EventHandler(button_Click);
sampleToolStrip.Items.Add(button);
//在主法度的底端添加ToolStrip
application.BottomToolPanel.Controls.Add(sampleToolStrip);
}
}
void button_Click(object sender, EventArgs e)
{
MessageBox.Show("The first plugin");
}
//相干的文章首要講動態加載,所以卸載就不實現了
public void UnLoad()
{
throw new Exception("The method or operation is not implemented.");
}
public event EventHandler<EventArgs> Loading;
#endregion
}
}
接下來我們創建一個Windows Application工程讓主窗體派生於IApplication接口並實現IApplication接口的辦法和屬性,下來我們聲明1個MenuStrip和1個StatusStrip,讓他們分別停靠在窗口的頂部和底端,接下來我們聲明4個ToolStripPanel,分別人他們停靠在高低閣下四個邊,最後我們創建一個ToolStrip,在上邊添加一個按鈕,當點擊這個按鈕的時辰,我們動態的加載插件。
爲了便利演示,我們把生成的Assembly放置到固定的地位,以便利主法度加載,在本例裏,我們在應用法度地點的文件夾裏創建一個子文件夾Plugins(E:\Practise\PluginSample\PluginSample\bin\Debug\Plugins),將插件工程產生的Assembly(FirstPlugin.dll)放置在這個子文件夾。下面是動態加載的代碼:
private void toolStripButton1_Click(object sender, EventArgs e)
{
//動態加載插件,爲了便利起見,我直接給出插件地點的地位
String pluginFilePath = Path.GetDirectoryName(Application.utablePath) + "\\plugins\\FirstPlugin.dll";
Assembly assembly = Assembly.LoadFile(pluginFilePath);
//獲得Assembly中的所有類型
Type[] types = assembly.GetTypes();
//遍歷所有的類型,找到插件類型,並創建插件實例並加載
foreach (Type type in types)
{
if (type.GetInterface("IPlugin") != null)//斷定類型是否派生自IPlugin接口
{
IPlugin plugin = (IPlugin)Activator.CreateInstance(type);//創建插件實例
plugin.Application = this;
plugin.Load();
}
}
}
(四)辦事
在(二)訂立契約一文中,可以看到我們的IApplication接口是派生於IServiceContainer接口的。爲什麼要派生於IServiceContainer呢?我們來看看IServiceContainer的定義,它有幾個AddService辦法和RemoveService辦法以及從IserviceProvider持續過來的GetService辦法。Service本身是.NET設計時架構的根蒂根基,Service供給設計時對象接見某項功能的辦法實現,說起來還真拗口。就我看來,ServiceContainer機制的本質就是解耦合,就是將類型的設計時功能從類型本身剝離出來。若是你把類型的設計時功能也封裝到類型裏,如許的類型包含了很多隻有開闢人員纔會用到而終極用戶底子不須要的功能,使得類型既癡肥有不便於擴大。而將設計時功能剝離出來,如許類型就可以不依附於特定的設計景象,之所以如今有這麼多非官方的.NET設計景象可能就是這個原因吧。
我們的插件式的應用法度框架正好也須要如許一個疏鬆的架構,我就移花接木把它應用到我們的框架中。
ServiceContainer是.NET供給的IserviceContainer的實現,若是沒有特別的須要我們不必擴大它,而是直接的哄騙它。在上一篇文章中我們在實現IApplication接口的時辰就直接應用的ServiceContainer。我們在應用Service架構的時辰,老是偏向於有一個根容器,各個Service容器構成了一個Service容器樹,每一個節點的辦事都可以一向向上傳遞,直到根部,而每一個節點懇求Service的時辰,我們老是可以從根節點獲得。我把這個根節點比方成一個辦事中間,它彙總了所有可供給的辦事,當某個對象要懇求辦事(GetService)只須要向根結點發送要獲得的辦事,根結點就可以把辦事的對象傳遞給它。
從別的一個角度看,ServiceContainer爲我們的插件是應用法度供給了有力的支撐,哄騙ServiceContainer,你不單可以獲得應用法度所供給的所有的功能,並且你還可以經由過程插件嚮應用法度添加Service,而你添加的Service又可以辦事別的的Service,如許我們的應用法度框架就加倍的靈活了。然則任何器材都是有兩面性的,帶來靈活的同時也爲開闢人員的工作增長了錯雜度,所以應用ServcieContianer開闢的應用法度必須供給足夠具體的文檔,不然開闢人員可能底子不知道你到底有幾許Service可以用,因爲很多的Service是經由過程插件供給的,可能應用法度的作者都不會知道法度公佈今後會呈現幾許Service。
(五)經管插件
我們如今已經搭建了插件式的應用法度框架,接下來的工作就是要充分框架的內容,供給根蒂根基的辦事,也就是Service。我想首要的任務就是供給插件的經管辦事,我在前面的文章也提到了,要實現動態加載必必要知道插件寄宿在哪裏,哪些要加載,哪些不加載,這些就是這篇文章要評論辯論的題目。
起首解決的就是插件放在什麼處所,我採取的傳統的辦法,將插件放到應用法度地點目次下的制訂目次,我會在應用法度地點的目次下創建一個文件夾,定名爲Plugins。接下來的工作就是要通知哪些插件是要加載的,哪些是不須要加載的,我會將這些信息放到應用法度的設備文件中的制訂設備塊中,當應用法度運行的時辰,就會讀取設備文件,並按照獲得的信息加載插件。別的我們的應用法度框架是建樹在Service根蒂根基之上,所以我須要創建一個經管插件的service。
我們如今定義一個插件經管的Service接口。
using System;
using System.Collections.Generic;
using System.Text;
namespace PluginFramework
{
public interface IPluginService
{
IApplication Application { get;set;}
void AddPlugin(String pluginName, String pluginType, String Assembly, String pluginDescription);
void RemovePlugin(String pluginName);
String[] GetAllPluginNames();
Boolean Contains(String pluginName);
Boolean LoadPlugin(String pluginName);
Boolean UnLoadPlugin(String pluginName);
IPlugin GetPluginInstance(String pluginName);
void LoadAllPlugin();
}
}
PluginService要實現的目標起首是在設備文件中添加/刪除要加載的插件以及相干的信息,接下來就是動態的加載插件。我們要定義幾個類型:Plugin設備區塊類型,Plugin元素類型,plugin元素湊集類型,以便我們可以或許讀取插件的信息。
最後我們實現PluginService:
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Configuration;
using System.Reflection;
using System.Windows.Forms;
using System.IO;
using System.Collections;
namespace PluginFramework
{
public class PluginService : IPluginService
{
private IApplication application = null;
private PluginConfigurationSection config = null;
private Dictionary<String, IPlugin> plugins = new Dictionary<string, IPlugin>();
private XmlDocument doc = new XmlDocument();
public PluginService()
{
}
public PluginService(IApplication application)
{
this.application = application;
}
IPluginService Members#region IPluginService Members
public void AddPlugin(string pluginName, string pluginType, string assembly, string pluginDescription)
{
doc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
XmlNode pluginNode = doc.SelectSingleNode("/configuration/PluginSection");
XmlElement ele = doc.CreateElement("add");
XmlAttribute attr = doc.CreateAttribute("Name");
attr.Value = pluginName;
ele.SetAttributeNode(attr);
XmlAttribute attrType = doc.CreateAttribute("Type");
attrType.Value = pluginType;
ele.SetAttributeNode(attrType);
XmlAttribute attrAss = doc.CreateAttribute("Assembly");
attrAss.Value = assembly;
ele.SetAttributeNode(attrAss);
XmlAttribute attrDes = doc.CreateAttribute("Description");
attrDes.Value = pluginDescription;
ele.SetAttributeNode(attrDes);
pluginNode.AppendChild(ele);
doc.Save(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
ConfigurationManager.RefreshSection("PluginSection");
}
public void RemovePlugin(string pluginName)
{
doc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
XmlNode node = doc.SelectSingleNode("/configuration/PluginSection");
foreach (XmlNode n in node.ChildNodes)
{
if (n.Attributes != null)
{
if (n.Attributes[0].Value == pluginName)
{
node.RemoveChild(n);
}
}
}
doc.Save(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
ConfigurationManager.RefreshSection("PluginSection");
}
public string[] GetAllPluginNames()
{
config = (PluginConfigurationSection)ConfigurationManager.GetSection("PluginSection");
PluginConfigurationElement pe = new PluginConfigurationElement();
ArrayList ps = new ArrayList();
for (Int32 i = 0; i < config.PluginCollection.Count; i++)
{
pe = config.PluginCollection[i];
ps.Add(pe.Name);
}
return (String[])ps.ToArray(typeof(String));
}
public bool Contains(string pluginName)
{
config = (PluginConfigurationSection)ConfigurationManager.GetSection("PluginSection");
PluginConfigurationElement pe = new PluginConfigurationElement();
List<String> ps = new List<string>();
for (Int32 i = 0; i < config.PluginCollection.Count; i++)
{
pe = config.PluginCollection[i];
ps.Add(pe.Name);
}
return ps.Contains(pluginName);
}
public bool LoadPlugin(string pluginName)
{
Boolean result = false;
config = (PluginConfigurationSection)ConfigurationManager.GetSection("PluginSection");
PluginConfigurationElement pe = new PluginConfigurationElement();
String path = Path.GetDirectoryName(System.Windows.Forms.Application.utablePath) + "\\Plugin";
try
{
for (Int32 i = 0; i < config.PluginCollection.Count; i++)
{
pe = config.PluginCollection[i];
if (pe.Name == pluginName)
{
Assembly assembly = Assembly.LoadFile(path + "\\" + pe.Assembly);
Type type = assembly.GetType(pe.Type);
IPlugin instance = (IPlugin)Activator.CreateInstance(type);
instance.Application = application;
instance.Load();
plugins[pluginName] = instance;
result = true;
break;
}
}
if (!result)
{
MessageBox.Show("Not Found the Plugin");
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
result = false;
}
return result;
}
public bool UnLoadPlugin(string pluginName)
{
Boolean result = false;
try
{
IPlugin plugin = GetPluginInstance(pluginName);
plugin.UnLoad();
result = true;
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
return result;
}
public void LoadAllPlugin()
{
PluginConfigurationElement pe = new PluginConfigurationElement();
config = (PluginConfigurationSection)ConfigurationManager.GetSection("PluginSection");
String path = Path.GetDirectoryName(System.Windows.Forms.Application.utablePath) + "\\Plugin";
try
{
for (Int32 i = 0; i < config.PluginCollection.Count; i++)
{
pe = config.PluginCollection[i];
Assembly assembly = Assembly.LoadFile(path + "\\" + pe.Assembly);
Type type = assembly.GetType(pe.Type);
IPlugin instance = (IPlugin)Activator.CreateInstance(type);
instance.Application = application;
instance.Load();
plugins[pe.Name] = instance;
}
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
public IApplication Application
{
get
{
return application;
}
set
{
application = value;
}
}
public IPlugin GetPluginInstance(string pluginName)
{
IPlugin plugin = null;
if (plugins.ContainsKey(pluginName))
{
plugin = plugins[pluginName];
}
return plugin;
}
#endregion
}
}
因爲代碼鬥勁多,我也就不一一列舉了,只把鬥勁首要的代碼列出來,其餘的我會供給源代碼的下載。在實現了PluginService今後,我們須要有一個處所可以或許應用這個Service來經管插件,我的做法是在一個菜單裏添加一個項目,當用戶點擊這個項目標時辰彈出插件經管的對話框,用戶在這個對話框中選擇應用那些插件,當插件被選中的時辰,插件會被立即加載進來,並且記錄到設備文件裏,當用戶下次運行應用法度的時辰,插件默認會被主動的加載。
別的從如今開端我們就須要應用設備文件了,所以,我們須要給應用法度添加一個app.config文件,文件內容如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section
name="PluginSection"
type="PluginFramework.PluginConfigurationSection, PluginFramework"
/>
</configSections>
<PluginSection>
</PluginSection>
</configuration>
(六)通信機制
這個系列的文章寫到這裏,也該談談這個題目了,畢竟成果已經有了插件經管。不知道大師有沒有重視到我在第四篇文章裏談到的辦事容器(Service Container),Service是我所提到的插件式的應用法度框架的根蒂根基,我們也可以回頭看看IApplication的接口定義,IApplication是派生於IServiceContainer。我把應用法度供給的相干的功能抽象成一個一個的Service,比如文檔經管的,我們就抽象成IDocumentService,停靠對象欄經管功能抽象成IDockBarService,菜單經管的功能抽象成IMenuService,等等。我在第四篇文章裏也提到了“我們在應用Service架構的時辰,老是偏向於有一個根容器,各個Service容器構成了一個Service容器樹,每一個節點的辦事都可以一向向上傳遞,直到根部,而每一個節點懇求Service的時辰,我們老是可以從根節點獲得。我把這個根節點比方成一個辦事中間,它彙總了所有可供給的辦事,當某個對象要懇求辦事(GetService)只須要向根結點發送要獲得的辦事,根結點就可以把辦事的對象傳遞給它。”
IApplication是從IServiceContainer接口派生出來的,而我們的應用法度主窗口又是從IApplication接口派生出來的,所以,我們的應用法度主窗口就是一個ServiceContainer。從IPlugin的定義來看,它有一個IApplication接口屬性,這個IApplication屬性是什麼時辰指定的呢,在第五篇文章的源代碼裏我們看到,當每一個Plugin被實例化的時辰,由PluginService指定的,所以在每一個Plugin被Load之前,IApplication已經被指定,而代表這個IApplication接口的實例恰是我們的應用法度主窗口,而它恰是我們所須要的辦事容器。一旦我們可以或許獲得IApplication實例,我們就可以獲得全部應用法度所供給的所有的辦事。假設我們要獲得文檔辦事,就可以應用Plugin的Application.GetService(typeof(IdocumentService))來獲得文檔辦事的實例,接着我們就可以應用這個實例來完成某項功能,比如添加一個新文檔等等,其實在第五篇文章的源代碼就有如許代碼:
private void CheckExistedPlugin()
{
IPluginService pluginService = (IPluginService)application.GetService(typeof(IPluginService));
if (pluginService != null)
{
List<String> nameList=new List<string>();
String[] pluginNames = pluginService.GetAllPluginNames();
nameList.AddRange(pluginNames);
foreach (ListViewItem item in listView1.Items)
{
if (nameList.Contains(item.Text))
{
item.Checked = true;
}
}
}
}
當然,要在插件中獲得實例,你必須在應用法度裏或者其他插件裏實例化辦事對象,然後添加到辦事容器裏,還拿上邊的例子,我們在應用法度裏實例化了PluginService,然後添加到了容器裏,代碼如下:
public MainForm()
{
InitializeComponent();
pluginService = new PluginService(this);
serviceContainer.AddService(typeof(IPluginService), pluginService);
}
稍後,我會持續完美這個例子,做一個簡單的多文檔編輯器來做演示,並供給一些根蒂根基的辦事,以便大師瀏覽
(七)根蒂根基辦事
既然做好了框架,我們就慾望爲某個目標辦事,我們要供給一些根蒂根基的辦事,便哄騙戶持續擴大他的功能。起首想到的功能就是,菜單,對象欄的經管,接下來我們要實現一些更風行的功能,比如停靠對象欄等等。
如何實現這些辦事呢?我們慾望我們的插件在運行時可以獲得應用法度本身的菜單,對象條,停靠對象欄等等,然後向他們添加項目,比如參加一個菜單項,添加一個對象欄按鈕。爲了在運行時獲得某個菜單或者對象欄,我們要爲每一個菜單後者對象欄分派一個Key,然後放到一個詞典中,當須要的時辰,我們經由過程這個key來獲得實例。對於這個Key呢,在我的例子鬥勁簡單就是他的名字,我們來看看ToolStripService的代碼:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace PluginFramework
{
public class ToolStripService:IToolStripService
{
private IApplication application = null;
private Dictionary<String, ToolStrip> toolStrips = new Dictionary<string, ToolStrip>();
public ToolStripService(IApplication application)
{
this.application = application;
}
IToolStripService Members#region IToolStripService Members
public System.Windows.Forms.ToolStrip GetToolStrip(string toolStripName)
{
ToolStrip toolStrip = null;
if (toolStrips.ContainsKey(toolStripName))
{
toolStrip = toolStrips[toolStripName];
}
return toolStrip;
}
public void AddToolStrip(string toolStripName, System.Windows.Forms.ToolStrip toolStrip)
{
if (toolStrips.ContainsKey(toolStripName))
{
MessageBox.Show("The tool strip name has existed!");
}
else
{
toolStrips[toolStripName] = toolStrip;
//若是沒有指定toolstrip在哪個面板,擇默認加到頂部
if (application.TopToolPanel != null)
{
application.TopToolPanel.Controls.Add(toolStrip);
}
}
}
public void AddToolStrip(string toolStripName, System.Windows.Forms.ToolStrip toolStrip, ToolStripDockState option)
{
if (toolStrips.ContainsKey(toolStripName))
{
MessageBox.Show("The tool strip name has existed!");
}
else
{
toolStrips[toolStripName] = toolStrip;
switch (option)
{
case ToolStripDockState.Left:
if (application.LeftToolPanel != null)
{
application.LeftToolPanel.Controls.Add(toolStrip);
}
break;
case ToolStripDockState.Right:
if (application.RightToolPanel!= null)
{
application.RightToolPanel.Controls.Add(toolStrip);
}
break;
case ToolStripDockState.Top:
if (application.TopToolPanel != null)
{
application.TopToolPanel.Controls.Add(toolStrip);
}
break;
case ToolStripDockState.Bottom:
if (application.BottomToolPanel != null)
{
application.BottomToolPanel.Controls.Add(toolStrip);
}
break;
}
}
}
public void RemoveToolStrip(string toolStripName)
{
ToolStrip toolStrip = GetToolStrip(toolStripName);
if (toolStrip != null)
{
if (application.TopToolPanel != null && application.TopToolPanel.Controls.Contains(toolStrip))
{
application.TopToolPanel.Controls.Remove(toolStrip);
}
else if (application.BottomToolPanel != null && application.BottomToolPanel.Controls.Contains(toolStrip))
{
application.BottomToolPanel.Controls.Remove(toolStrip);
}
else if (application.LeftToolPanel != null && application.LeftToolPanel.Controls.Contains(toolStrip))
{
application.LeftToolPanel.Controls.Remove(toolStrip);
}
else if (application.RightToolPanel != null && application.RightToolPanel.Controls.Contains(toolStrip))
{
application.RightToolPanel.Controls.Remove(toolStrip);
}
}
toolStrips.Remove(toolStripName);
}
#endregion
}
}
對於視圖或者是停靠對象欄來說,最好是不要直接在詞典中放入實例,而是應當將對象的類型放入到詞典中,因爲,視圖和停靠對象欄本身都是從Form派生而來,所以,當視圖或者是停靠對象欄被封閉的時辰,對象就被燒燬了,而對象的創建在是插件的Load辦法裏完成的,我們不成能再去調用插件的Load辦法,如許給我們的應用帶來了不便,所以我們應當註冊類型,然後在Service中實現一個Show辦法是鬥勁公道的,這裏爲了演示便利,我就直接在Load裏面實例化了,並把實例放到了詞典裏。
下邊這個圖例裏顯示了插件參加的停靠對象欄,對象欄,一個新的菜單“View”和View菜單的子菜單:
(八)視圖辦事的簡單實現
我在前一篇文章裏提到,對於停靠對象欄或者是視圖最好是不要將實例放到詞典中,而是將對象欄或者視圖的類型放到詞典中,因爲視圖類型會經常的被重用,並且會經常被封閉或者再打開。當實例被封閉後,資料就被開釋了,對於實例的經管就會鬥勁麻煩,所以我們分爲兩步走。在插件被加載的時辰,我們只註冊類型,在應用法度運行的時辰,我們經由過程某種路子來實例化他。
我批改的以前的例子,首要凸起本次演示的功能。此次的例子實現的功能是經由過程插件擴大應用法度處理懲罰不合文件的才能。在原始的應用法度中,我們可以經由過程File菜單的Open,只能打開一種文件,就是文本文件,大師可以在例子中看到,當我們沒有加載插件的景象下,在OpenFileDialog的Filter中只有"Text(*.txt)"。選擇一個文本文件今後,將會呈現文本文件視圖。當我們加載插件今後,在點擊File->Open菜單,我們調查Filter,發明會多出兩種文件:"JPEG"和"BMP",這是我們就可以打開圖片文件,選中文件今後,將會呈現Picture視圖,並且在主菜單下邊,還會呈現一個對象欄,點擊對象欄上的按鈕,可以給圖片加上水印,並且對象欄會按照PictureView的狀況(Active)顯示和消散。比如你打開了一個文本視圖和一個圖片視圖,當你切換到文本視圖的時辰,對象欄就會消散,再切換到圖片視圖的時辰,對象欄又會呈現。
我在框架裏面添加了一個IDocumentViewService的接口,用以描述辦事的功能:
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
namespace PluginFramework
{
public interface IDocumentViewService
{
void RegisterView(String fileType,string fileFilter,Type viewType);
void ShowView(String fileType, String filePath);
void RemoveRegister(String fileType);
String GetFileFilter(String fileType);
String GetFileTypeByFileFilter(String fileFilter);
StringCollection FileTypies { get;}
}
}
下面是這個辦事的實現:
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
namespace PluginFramework
{
public class DocumentViewService:IDocumentViewService
{
private Dictionary<String, Type> docViewRegister = new Dictionary<string, Type>();
private Dictionary<String, String> fileTypeToFileFilter = new Dictionary<string, string>();
private Dictionary<String, String> fileFilterToFileType = new Dictionary<string, string>();
private IApplication application = null;
public DocumentViewService(IApplication app)
{
application = app;
}
IDocumentViewService Members#region IDocumentViewService Members
public void RegisterView(string fileType, string fileFilter, Type viewType)
{
docViewRegister[fileType] = viewType;
fileTypeToFileFilter[fileType] = fileFilter.ToUpper();
fileFilterToFileType[fileFilter.ToUpper()] = fileType;
}
public void ShowView(string fileType, string filePath)
{
if(docViewRegister.ContainsKey(fileType))
{
IDocumentView docView = null;
try
{
docView = (IDocumentView)Activator.CreateInstance(docViewRegister[fileType]);
docView.Application = application;
docView.ShowView(filePath);
}
catch
{
}
}
}
public void RemoveRegister(string fileType)
{
docViewRegister.Remove(fileType);
}
public StringCollection FileTypies
{
get
{
StringCollection sc = new StringCollection();
foreach (String key in docViewRegister.Keys)
{
sc.Add(key);
}
return sc;
}
}
#endregion
IDocumentViewService Members#region IDocumentViewService Members
public string GetFileFilter(string fileType)
{
String result = "";
if (fileTypeToFileFilter.ContainsKey(fileType))
{
result = fileTypeToFileFilter[fileType];
}
return result;
}
#endregion
IDocumentViewService Members#region IDocumentViewService Members
public string GetFileTypeByFileFilter(string fileFilter)
{
String result = "";
if (fileFilterToFileType.ContainsKey(fileFilter))
{
result = fileFilterToFileType[fileFilter];
}
return result;
}
#endregion
}
}