插件式编程之使用反射分离MDI的父窗口和子窗口
作者:sagahu@163.com,发表于:http://www.cnblogs.com/sagahu,2012-10-09
现在越来越多的软件,包括许多的管理系统,都在使用“主体+插件”式的结构,具有很大的优越性。例如主体与各插件分别开发,便于团队合作;插件“即插即用”,便于定制系统功能;插件模块可方便的单独升级等等。
插件式结构的本质在于不修改主体程序的情况下,通过插件模块来对软件功能进行扩展或者修订。
主体+插件式结构是将一个软件分为两部分。一部分为程序的主体模块,实现程序的主界面框架与主控调度。另一部分为遵循系统整体结构预先约定的某项/某方面的功能模块,可以方便地往主体“即插即用”。
下面使用具体的案例来探讨这种编程方式。
我们经常发现很多的管理系统使用MDI式的父/子窗口界面,初学者通常是把父子窗口放在同一个项目里,其实它们也是可以分离的。下面使用VS2005/C#来逐步实现。
在VS里建立一个解决方案,建3个项目:
(1)类库:MdiLib,定义MDI父窗口与子窗口的规范;
(2)类库:FuncModule1,模拟一个功能插件,准备把MDI子窗口放在这里面;
(3)WindowsApplication:MainModuleDynamic,模拟程序主体,实现主窗口与主控调度。
注意:(1)进入每个项目的属性页,把它们的生成输出路径都设置为:..\bin,便于使用反射机制来动态加载程序集。(2)MainModuleDynamic项目引用MdiLib项目,FuncModule1项目也引用MdiLib项目。
在MdiLib项目里添加两个接口:
public interface IMainForm
{
void SetStatusText(string statusInfo); // 便于子窗口给父窗口设置状态信息
}
public interface IFuncForm // 规范化子窗口的编码
{
bool GetData();
void BindData();
}
在FuncModule1项目里添加两个Form,一个名为StudentForm,另一个名为TeacherForm。两个Form都是简单地设置窗口Text分别为“学生名单”、“教师名单”;上面各放一个DataGridView,改名为“gridData”。
StudentForm代码如下:
using MdiLib;
namespace FuncModule1
{
public partial class StudentForm : Form, IFuncForm
{
// 修改构造函数
private StudentForm(IMainForm mainForm)
{
InitializeComponent();
this._MainForm = mainForm;
}
private IMainForm _MainForm;
// 本窗体用的数据容器
IList<Student> list = null;
// 学生名单功能的唯一入口方法
public static void ShowMe(IMainForm mainForm)
{
StudentForm frm = null;
foreach (Form mdiChild in (mainForm as Form).MdiChildren)
{
if (mdiChild.Text == "学生名单")
{
frm = mdiChild as StudentForm;
break;
}
}
if (frm != null)
{
if (frm.WindowState == FormWindowState.Minimized)
frm.WindowState = FormWindowState.Maximized;
frm.Activate();
}
else
{
frm = new StudentForm(mainForm);
frm.MdiParent = (Form)mainForm;
if (frm.GetData()) // 如果获取数据发生错误,再显示窗口就没有意义
{
frm.BindData();
frm.Show();
}
}
}
#region IFuncForm 成员
public bool GetData()
{
// 模拟获得数据
try
{
this.list = new List<Student>();
list.Add(new Student("小马", 18, "男"));
list.Add(new Student("小牛", 17, "男"));
list.Add(new Student("小朱", 19, "女"));
list.Add(new Student("小杨", 18, "男"));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
return true;
}
public void BindData()
{
this.gridData.DataSource = this.list;
}
#endregion
}
// 仅供演示用
public class Student
{
public Student(string name, int age, string sex)
{
this._Name = name;
this._Age = age;
this._Sex = sex;
}
private string _Name;
public string Name
{
get { return _Name; }
set { _Name = value; }
}
private int _Age;
public int Age
{
get { return _Age; }
set { _Age = value; }
}
private string _Sex;
public string Sex
{
get { return _Sex; }
set { _Sex = value; }
}
}
}
TeacherForm代码与StudentForm代码非常类似,也贴在下面:
using MdiLib;
namespace FuncModule1
{
public partial class TeacherForm : Form, IFuncForm
{
// 修改构造函数
private TeacherForm(IMainForm mainForm)
{
InitializeComponent();
this._MainForm = mainForm;
}
private IMainForm _MainForm;
// 本窗体用的数据容器
IList<Teacher> list = null;
// 教师名单功能的唯一入口方法
public static void ShowMe(IMainForm mainForm)
{
TeacherForm frm = null;
foreach (Form mdiChild in (mainForm as Form).MdiChildren)
{
if (mdiChild.Text == "教师名单")
{
frm = mdiChild as TeacherForm;
break;
}
}
if (frm != null)
{
if(frm.WindowState ==FormWindowState.Minimized)
frm.WindowState = FormWindowState.Maximized;
frm.Activate();
}
else
{
frm = new TeacherForm(mainForm);
frm.MdiParent = (Form)mainForm;
if (frm.GetData()) // 如果获取数据发生错误,再显示窗口就没有意义
{
frm.BindData();
frm.Show();
}
}
}
#region IFuncForm 成员
public bool GetData()
{ // 模拟获得数据
try
{
this.list = new List<Teacher>();
list.Add(new Teacher("张三", "语文"));
list.Add(new Teacher("李四", "数学"));
list.Add(new Teacher("王五", "英语"));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
return true;
}
public void BindData()
{
this.gridData.DataSource = this.list;
}
#endregion
}
// 仅供演示用
public class Teacher
{
public Teacher(string name, string course)
{
this._Name = name;
this._Course = course;
}
private string _Name;
public string Name
{
get { return _Name; }
set { _Name = value; }
}
private string _Course;
public string Course
{
get { return _Course; }
set { _Course = value; }
}
}
}
这样,我们在FuncMoudle1里面模拟实现了一些单独的程序功能,包括其界面,看起来系统结构很良好。下面实现程序主体模块。
在MainModuleDynamic里加入一个Form:
(1)命名为:MainForm;Text修改为“主窗口”。
(2)修改IsMdiContainer为true。
(3)放一个timer,命名为:timerStatusRestore,设置时间间隔为:1000。
(4)放一个ToolStrip,其自动命名为:toolStrip1。
(5)放一个MenuStrip,其自动命名为:menuStrip1。增加一个菜单项:mnuFile,Text为“File”。准备自动往其下面生成子菜单。
(6)放一个StautsStrip,其自动命名为:statusStrip1。增加一个ToolStripStatusLabel,改名为:tsStatus ,Text设为:“良好”, Spring设为true。再增加一个ToolStripStatusLabel,随便了。
这个窗体的代码如下:
using MdiLib;
using System.Reflection;
namespace MainModuleDynamic
{
public partial class MainForm : Form, IMainForm
{
public MainForm()
{
InitializeComponent();
}
#region 主窗口状态栏管理
#region IMainForm 成员
public void SetStatusText(string statusInfo)
{
this.tsStatus.Text = statusInfo;
this.timerStatusRestore.Start();
}
private void timerStatusRestore_Tick(object sender, EventArgs e)
{
this.SetStatusText("良好");
this.timerStatusRestore.Stop();
}
private void MainForm_MdiChildActivate(object sender, EventArgs e)
{
this.SetStatusText(this.ActiveMdiChild.Text);
}
#endregion
#endregion
#region 根据用户权限许可动态生成菜单
private void CreateMenus()
{
IList<FuncEntryItem> list = new List<FuncEntryItem>();
list.Add(new FuncEntryItem("学生名单", "FuncModule1", "StudentForm", "ShowMe"));
list.Add(new FuncEntryItem("教师名单", "FuncModule1", "TeacherForm", "ShowMe"));
foreach (FuncEntryItem func in list)
{
ToolStripMenuItem tsmi = new ToolStripMenuItem();
tsmi.Text = func.FuncName;
tsmi.Size = new System.Drawing.Size(152, 22);
tsmi.Click += new EventHandler(this.MenuItemClick);
this.mnuFile.DropDownItems.Add(tsmi);
tsmi.Tag = func;
ToolStripButton tsb = new ToolStripButton();
tsb.Text = func.FuncName;
tsb.Size = new System.Drawing.Size(51, 22);
tsb.Click += new EventHandler(this.MenuItemClick);
this.toolStrip1.Items.Add(tsb);
tsb.Tag = func;
}
}
private void MenuItemClick(object sender, EventArgs e)
{
FuncEntryItem func = (FuncEntryItem)((ToolStripItem)sender).Tag;
#region 使用反射实现调用功能模块的静态方法(那个入口方法)
Assembly asmFuncMoudle = Assembly.Load(func.AssemblyName); // 动态加载功能模块的Assembly
Type funcType = asmFuncMoudle.GetType(func.AssemblyName + "." + func.EntryClassName); // 根据功能模块类名获得该类型
Type[] parameterTypes = new Type[1]; // 预先知道我们设计的该功能模块入口方法只有一个参数
Assembly asmMdiLib = Assembly.Load("MdiLib"); // 动态加载MdiLib的Assembly
parameterTypes[0] = asmMdiLib.GetType("MdiLib.IMainForm"); // 这个唯一参数的类型是MdiLib.IMainForm
MethodInfo funcMethod = funcType.GetMethod(func.EntryMethodName, parameterTypes); // 根据功能模块入口方法名称获得MethodInfo
object[] parameters = new object[1]; parameters[0] = this; // 这个唯一参数的传入值是this
funcMethod.Invoke(null, parameters); // 可以调用这个静态方法了
#endregion
}
#endregion
private void MainForm_Load(object sender, EventArgs e)
{
this.CreateMenus();
}
}
// 功能调用数据结构
public class FuncEntryItem
{
public FuncEntryItem(string funcName, string assemblyName, string entryClassName, string entryMethodName)
{
this._FuncName = funcName;
this._AssemblyName = assemblyName;
this._EntryClassName = entryClassName;
this._EntryMethodName = entryMethodName;
}
private string _FuncName;
public string FuncName
{
get { return _FuncName; }
set { _FuncName = value; }
}
private string _AssemblyName;
public string AssemblyName
{
get { return _AssemblyName; }
set { _AssemblyName = value; }
}
private string _EntryClassName;
public string EntryClassName
{
get { return _EntryClassName; }
set { _EntryClassName = value; }
}
private string _EntryMethodName;
public string EntryMethodName
{
get { return _EntryMethodName; }
set { _EntryMethodName = value; }
}
}
}
运行效果示例如下:
愿这个示例可以作为管理系统实现插件式编程的启发,非常希望与同好者交流。