關於C插件編程和插件宿主數據傳遞的一些方法
最近做了一個C#的插件式編程的項目,涉及到了在winform下插件和宿主的數據傳遞。希望實現插件主動向宿主傳遞數據而不是宿主通過運行一個方法去索要數據。
具體實現是Form1為宿主,在textbox里輸入字元串可以傳到插件里。
另外是可以在插件里通過滑鼠繪圖,單擊確定將圖傳遞到宿主。
考慮到插件編程,必然涉及到介面,這裡我定義介面
[csharp] view plain copy
namespace
interclass- {
public
delegate
void
ChangeDataHandler(List<List<Point>> ptlists); //定義委託public
interface
inter- {
- DataTransfer DataTransfer {
get
;set
; } string
WhoAmI(DataTransfer DT); //傳遞DataTransfer進入插件同時獲得插件的名字void
Action(
object
sender, EventArgs e); //插件程序入口- List<List<Point>> PointLists {
get
; } event
ChangeDataHandler ChangeData; //定義事件- }
public
class
DataTransfer- {
private
string
testdata;public
string
Testdata {get
=> testdata;
set
=> testdata = value; }- }
- }
參考跨線程和跨窗體傳遞數據的相關資料,通過委託可以實現插件向宿主主動傳遞。
在宿主插件中添加動態菜單,綁定單擊事件到Action,以開始插件的運行。
下面是宿主窗體載入插件的代碼,包含動態菜單的操作。
[csharp] view plain copy
private
List<inter> _plugIns =new
List<inter>(); //插件列表
[csharp] view plain copy
int
_countPlugAdded = 0; //插件成功載入計數
[csharp] view plain copy
int
_countPlugAddError = 0; //插件載入失敗計數- DataTransfer MainDT; //數據傳遞的實例
[csharp] view plain copy
[csharp] view plain copy
[csharp] view plain copy
private
void
Form1_Load(object
sender, EventArgs e)- {
- List<
string
> list =new
List<string
>();
- MainDT =
new
DataTransfer(); - //讀取插件
string
path = System.IO.Path.Combine(System.Environment.CurrentDirectory + @"dlls");- DirectoryInfo dir =
new
DirectoryInfo(path); - FileInfo[] fil = dir.GetFiles();
foreach
(FileInfo fin
fil)- {
if
(f.Extension.Equals(".dll"))- {
- list.Add(f.FullName);
- }
- }
- //載入插件
if
(list.Count != 0)- {
foreach
(string
tpin
list)
- {
try
- {
- inter tempI;
- Assembly asd = Assembly.LoadFile(tp);
- Type[] t = asd.GetTypes();
foreach
(Type tinin
t)- {
if
(tin.GetInterface("inter") !=null
)- {
- tempI = (inter)Activator.CreateInstance(asd.GetType(tin.FullName));
- _plugIns.Add(tempI);
- tempI.ChangeData +=
new
ChangeDataHandler(myDataChanged); - //創建菜單
- ToolStripMenuItem newItem =
new
ToolStripMenuItem(); - newItem.Text = tempI.WhoAmI(MainDT);
- newItem.Click += tempI.Action;
- ((ToolStripMenuItem)(mainMenu.Items[1])).DropDownItems.Add(newItem);
- //計數累加
- _countPlugAdded++;
- //break;
- }
- }
- }
catch
(Exception)- {
- _countPlugAddError++;
- }
- }
- }
else
- {
- ToolStripMenuItem newItem =
new
ToolStripMenuItem();
- newItem.Text = "No Plug-In";
- newItem.Enabled =
false
; - ((ToolStripMenuItem)(mainMenu.Items[1])).DropDownItems.Add(newItem);
- }
- MessageBox.Show(_countPlugAdded.ToString() +
- "個插件成功載入," + _countPlugAddError.ToString() +
- "個插件載入失敗。");
- }
然後在插件里實現介面。首先說接收宿主數據的插件。
宿主將數據傳遞到插件
大致思路是傳遞DataTransfer實例進入插件(在載入插件並實例化的時候已經傳遞進去了),然後在插件里通過定時器掃描DataTransfer實例。當然也可以通過事件進行,我這裡偷懶使用了定時器。
因為介面定義並沒有和Form有關聯,所以在Action,也就是插件程序的入口點,要實例化子窗體顯示出來。
[csharp] view plain copy
namespace
plugin1- {
public
class
Class1 : inter- {
private
DataTransfer dataTransfer;public
DataTransfer DataTransfer {get
=> dataTransfer;set
=> dataTransfer = value; }
public
List<List<Point>> PointLists =>throw
new
NotImplementedException();//因為這個插件不向宿主傳遞數據所以不實現這個。public
event
ChangeDataHandler ChangeData;//因為這個插件不會產生向宿主傳遞數據所以不實現這個。public
void
Action(object
sender, EventArgs e)- {
- MessageBox.Show("Plugin1");//測試用,表示在Class1裡面,Action已經開始運行。
- FormT formT =
new
FormT();//實例化窗體 - formT.SetDT(dataTransfer);//SetDT是在FormT裡面定義的方法,用於把DataTransfer傳遞進FormT裡面。
- formT.Show();//顯示插件的子窗體
- }
public
string
WhoAmI(DataTransfer DT)- {
- dataTransfer =
new
DataTransfer(); - dataTransfer = DT;//把宿主的DataTransfer傳入插件
return
"plugin1";//返回自己的名字- }
- }
- }
這樣在宿主里單機菜單里的plugin1就可以運行這個插件的Action方法並顯示子窗體。
在插件的窗體裡面寫:
[csharp] view plain copy
namespace
plugin1- {
public
partialclass
FormT : Form- {
private
int
_counter = 0;- DataTransfer _dataTransfer;//傳入數據用
public
void
SetDT(DataTransfer inputDT) => _dataTransfer = inputDT;public
FormT()- {
- InitializeComponent();
- }
private
void
timer1_Tick(object
sender, EventArgs e)//通過定時器掃描DataTransfer實現宿主數據的同步顯示。- {
this
.Text = "這是一個插件創建的窗體,由插件控制。" + _counter.ToString();- _counter++;
- Monitor.Enter(_dataTransfer);
- label1.Text = _dataTransfer.Testdata;
- Monitor.Pulse(_dataTransfer);
- Monitor.Exit(_dataTransfer);
- }
private
void
FormT_Load(object
sender, EventArgs e)- {
this
.Text = "這是一個插件創建的窗體,由插件控制。" + _counter.ToString();- timer1.Enabled =
true
; - }
private
void
button1_Click(object
sender, EventArgs e)- {
- MessageBox.Show(_dataTransfer.Testdata,"我是插件創建的窗體");//通過點擊按鈕實現數據的傳入。
- }
- }
- }
[csharp] view plain copy
- 這裡我嘗試了兩種方法, 一個是通過定時器定時查看,另外是藉助button_click實現對DataTransfer掃描。
插件主動傳遞數據到宿主
宿主運行介面定義的方法確實可以實現讀取插件的信息,但是若插件需要主動傳遞數據到宿主這樣便不好用。假設現在需要在插件里通過滑鼠繪圖,單擊按鈕將這個圖傳遞到宿主里進行顯示。
需要在宿主里定義一個成員
[csharp] view plain copy
- List<List<Point>> _myPointLists =
new
List<List<Point>>();
記錄滑鼠的位置並依次連線。若抬起滑鼠則另外新建一個表。實現在插件里的繪圖的保存。
插件里實現介面:
[csharp] view plain copy
namespace
Plugin3- {
public
class
Class1 : inter- {
private
DataTransfer myDT;public
DataTransfer DataTransfer {get
=> myDT;set
=> myDT = value; }private
List<List<Point>> _pointLists;public
List<List<Point>> PointLists {get
=> _pointLists; }public
event
ChangeDataHandler ChangeData;public
void
Action(object
sender, EventArgs e)- {
- FormE formE =
new
FormE(); - formE.ChangePic +=
new
ChangePicHandler(picChanged); - formE.Show();
- }
public
string
WhoAmI(DataTransfer DT)- {
- _pointLists =
new
List<List<Point>>(); - myDT = DT;
return
"JustForFun";- }
private
void
picChanged(List<List<Point>> ptlists)- {
- _pointLists.Clear();
foreach
(List<Point> plin
ptlists)- {
- _pointLists.Add(
new
List<Point>()); foreach
(Point pin
pl)- {
- _pointLists[_pointLists.Count-1].Add(
new
Point()); - _pointLists[_pointLists.Count - 1][_pointLists[_pointLists.Count - 1].Count-1] = p;
- }
- }
- ChangeData?.Invoke(_pointLists);//執行委託實例 將數據從Class1傳遞到宿主
- }
- }
- }
和之前一樣,同樣使用Action作為入口,在Action中將formE裡面的ChangePic事件加上picChanged事件處理。
因為我們是在formE裡面繪圖,而不是在Class1中,所以我們要定義另外一個事件,也就是ChangePic來將數據從窗體傳遞到Class1中。這個和跨窗體傳遞數據的方法相同。
最後執行ChangeData委託實例。介面定義了ChangeData事件,在上面的宿主載入插件的代碼中,通過
[csharp] view plain copy
- tempI = (inter)Activator.CreateInstance(asd.GetType(tin.FullName));
- _plugIns.Add(tempI);
- tempI.ChangeData +=
new
ChangeDataHandler(myDataChanged);
實現了ChangeData事件和myDataChanged的事件處理。其中myDataChanged處理方法由宿主定義,而事件的發生由插件定義,實現了宿主和插件的分離。
在formE中,除了繪圖的相關方法外,在單擊「確定」按鈕中產生了ChangePic事件
[csharp] view plain copy
namespace
Plugin3- {
public
delegate
void
ChangePicHandler(List<List<Point>> ptlist); //定義委託public
partialclass
FormE : Form- {
bool
_mouseLeftDown =false
;- List<List<Point>> _drawPoints;
int
_drawCount = 0;int
pointNumber = 0;- Point myMousePosition;
public
event
ChangePicHandler ChangePic; //定義事件public
FormE()- {
- InitializeComponent();
- }
public
void
SetForm()- {
- }
private
void
FormE_Load(object
sender, EventArgs e)- {
- }
private
void
FormE_MouseDown(object
sender, MouseEventArgs e)- {
- }
private
void
FormE_MouseUp(object
sender, MouseEventArgs e)- {
- }
private
void
FormE_MouseMove(object
sender, MouseEventArgs e)- {
- }
private
void
FormE_Paint(object
sender, PaintEventArgs e)- {
- }
private
void
FormE_FormClosed(object
sender, FormClosedEventArgs e)- {
- }
- //Clear
private
void
button1_Click(object
sender, EventArgs e)- {
- _drawPoints.Clear();
- GC.Collect();
- _drawCount = 0;
- pointNumber = 0;
- Text = "點數:" + pointNumber.ToString();
- Invalidate();
- }
private
void
button2_Click(object
sender, EventArgs e)- {
- ChangePic?.Invoke(_drawPoints);//執行委託實例 將_drawPoints傳遞到上一級的Class1中
- }
- }
- }
FormE中的_drawPoints即是隨滑鼠移動不斷記錄當前滑鼠位置產生的列表。用於記錄在插件窗體裡面的繪圖。
由此便通過兩個事件將數據主動傳遞到了宿主中。這個過程里,只有宿主通過介面定義的ChangeData事件和宿主內實現的這個事件的處理方法是由宿主定義,至於DataChanged事件的發生和其他操作由插件定義,這也就是前面第一個只接收宿主數據不用實現這個事件的原因。宿主可以不關心插件是否要向宿主傳遞數據,何時傳遞數據。
※程序員如何快速搭建個性化主頁
※spring+mybatis 實現多數據源切換
TAG:程序員小新人學習 |