# 概述

为了增加程序的可扩展性,在主程序中添加了插件框架,以保证能在避免最大限度修改主体模块的基础上,添加新的功能和代码。

实现插件模块需要定义一个公共接口 PluginInterface,主程序通过这个接口来实现对插件具体功能的调用。插件模块需要在自行实现接口,并且传入所需要的常量值。

插件会在窗体初始化过程中被加载到进程空间中,并调用 Init (),在主窗体控件完成初始化和加载后调用 OnLoad (),之后框架会判断该插件是否具为周期执行,如果是则为其创建一个时钟并绑定周期事件为 RunLoop ()。主程序执行过程中会根据当前执行位置调用插件中满足当前 flag 的函数,此部分待后续补充。

具体实现流程如图:

# 插件接口的实现方式

接口定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//以下为接口定义
//功能需要在插件中进行实现
public interface IPlugin
{
//插件名
string Name { get; }
//插件执行位置标识
string RunFlag { get; }
//用来共享插件和主窗体之间的操作对象
Dictionary<string, object> Attributes { get; set; }
//插件详细信息
Information Info { get; }
//是否需要循环执行
bool IsLoop { get;}
//循环周期
int Interval { get; }
//用于显示当前插件的信息
string Hello()
//需要运行一次主函数
void Run();
//需要循环运行主函数
void RunLoop();
//插件被加载时运行的函数
void OnLoad();
//初始化插件,由主程序框架调用,在这里进行成员属性和各种变量的初始化操作
//其他操作和事件需要在初始化之后才能进行
//接受的参数 control:一般为主窗体控件
//需要插件间共享的属性需要在此处添加到运行时
void Init(Control control = null);
//注册回调函数(待实现)
void RegisterCallBackFunc(Action a);
//插件被销毁时运行的函数
void OnDestory();
}

具体代码实现包含几个部分:

  1. 引入接口模块,并继承接口
  2. 配置各类属性常量
  3. 实现所有的事件触发函数
  4. 设置所需要的触发事件

插件的具体实现举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
namespace PluginChart
{

#region 插件需要继承IPlugin接口
public class PChart : IPlugin
{
//设置各类属性常量
public string Name { get { return "绘图插件"; } }

public Information Info
{
get
{
Information info = new Information();
info.Name = "绘图插件";
info.CreateDate = "2022-10-03";
info.ModifyDate = "2022-10-13";
info.Version = "0.2.0";
info.Author = "Liu Chongyang";
info.Copyright = "Copyright © 2020-2022 Joee Cto. © \n All rights reserved.";
return info;

}
}
//是循环调用 返回 true 否则返回 false
public bool IsLoop { get { return true; } }
//设置时钟周期,仅当 IsLoop 为 true 时有效
public int Interval { get { return 100; } }

public Dictionary<string, object> Attributes { get; set; }

//因为是周期触发,没有调用flag,所以返回null
public string RunFlag { get { return null; } }

public string Hello()
{
return "用于实现绘制数据图表的插件";
}
#endregion


#region 定义需要用到的变量
//当前要绘制的点的序号
public int index = 0;

//本插件的控件
Control uControl;
Panel panel;
System.Windows.Forms.DataVisualization.Charting.Chart chart;

//用于接收主窗体控件
Form MainWnd;
TabPage tabPage_chart;

//当前存在的缺陷种类
public List<string> DefectTypes;
#endregion

#region 各类插件的实现
public void Init(Control control)
{
Attributes = new Dictionary<string, object>();
//创建用户控件实例
MainWnd = (Form)control;
uControl = new UserControl1();
panel = (Panel)uControl.Controls.Find("panel_global", true)[0];
chart = (Chart)uControl.Controls.Find("chart1", true)[0];

//初始化图表
chart.Series.Clear();
DefectTypes = new List<string>();
}

public void OnDestory()
{

}

public void OnLoad()
{
Console.WriteLine($"Onload excute : {Name}");
Run();
}

public void RegisterCallBackFunc(Action a)
{
throw new NotImplementedException();
}

public void Run()
{
//将控件绑定到主窗体中
tabPage_chart = (TabPage)MainWnd.Controls.Find("tabPage_chart", true)[0];
tabPage_chart.Controls.Clear();
tabPage_chart.Controls.Add(panel);
}
public void RunLoop()
{
//绘图实现
...

}
#endregion

# 插件的调用方式

主程序在启动时会检查 plugins 目录下有无符合条件的插件,具体条件为:

  1. 扩展名为 .dll 或 ***.plugin ***
  2. 是 .NET Framework >= 4.7 的扩展库
  3. 包含 IPlugin 的接口实现

当找到插件文件时,主程序会按前文所述方式进行插件初始化和调用,于是插件便可以实现在不修改主程序代码的情况下,对于主程序窗体的控件控制和修改。

如下图,是绘图插件中的自定义控件。

image-20221013163108472

1
2
3
4
5
6
7
public void Run()
{
//将控件绑定到主窗体中
tabPage_chart = (TabPage)MainWnd.Controls.Find("tabPage_chart", true)[0];
tabPage_chart.Controls.Clear();
tabPage_chart.Controls.Add(panel);
}

经过如上代码的操作后,自定义控件就可以添加到主程序窗体中,并且根据主程序中的数据来生成图表 (具体实现见开发日志 10.12)。

image-20221013163347892