Winform 窗口加载实例(一)-----MenuStrip
在开发软件的时候,常常会遇到这样的功能要求,根据不同的用户权限加载不同的功能界面。这样就需要我们在设计软件的时候动态添加添加功能。当然也可以把所有的功能都列出来,在根据权限来显示和隐藏功能,虽然这样也可以满足需求,但是需要很多额外的编码,性能上也可能大打折扣。
在WinForm编程中,MDI是一种很常用的结构。由于Windows界面的影响,很多时候我们设计软件的时候都喜欢上菜单下界面的模式。下面就结合代码来实现动态添加MenuStrip。
动态添加MenuStrip主要要解决三个方面的问题:
1、数据库设计
2、MenuStrip菜单的加载
3、菜单和窗口的结合
首先来解决数据库的设计问题。
一个窗口显示给用户看到的一般就是一个名称。另外菜单可能有级数,级数的解决一般有两种:根据编号来区分,设置父菜单。我喜欢根据编号来区分。比如:000 代表父菜单 000001代表一级菜单......如此类推。
数据库里面存储模块表的结构一般是 sBoardID sBoardName sFile sForm sMethod
sBoardID 菜单编号
sBoardName 菜单名称
sFile 菜单Form所在的文件(exe或者dll)
sForm 菜单对应的Form
sMethod 菜单对应的要执行的方法(有的菜单只有功能,没有界面,比如“退出系统”,“重新登录”)
其次来看看MenuStrip菜单的加载
因为菜单可能有多级,所以需要循环加递归来完成加载。会在下面的代码里面说明。
最后来看看菜单和窗口的结合
在数据库中,我们存储的都是字符串,而在程序中,我们却需要显示对应的窗口,这样就要求我们根据字符串来实例化对象。在.net里面我们可以用反射来实现。
上面说了,有些菜单只执行方法而没有界面,同样的数据库中存储的也只是字符串类型的方法名称,这样就要求我们根据字符串方法名执行对应的方法,自然而然的我们就会想到委托。
以上的问题都解决了,下面我们来看看代码:
menuMain是一个MenuStrip控件
private delegate void dlRunMethod();//定义一个委托,用于执行方法
//动态生成MenuStrip项
public void InstallMenus(ToolStripDropDownItem OwnerMenu, DataRow OwnerRow)
{
string strModule = OwnerRow == null ? "" : OwnerRow["sBoardID"].ToString();
//根据用户和父菜单来查询对应的子菜单,可换成你的查询代码
DataTable tabMenu = (new SysBoard()).QueryData(strModule,UserInfo.UserType);
//初始调用的时候,清空MenuStrip的所有内容
if (OwnerMenu == null) menuMain.Items.Clear();
if (tabMenu.Rows.Count == 0 && OwnerMenu == null) return;
//没有子级菜单,加载菜单单击事件
if (tabMenu.Rows.Count == 0)
{
Form frmCur = null;
if ((OwnerRow["sFile"].ToString().Length==0 || OwnerRow["sForm"].ToString().Length==0) && OwnerRow["sMethod"].ToString().Length==0)
OwnerMenu.Enabled = false;
else
{
//生成菜单的单击事件
OwnerMenu.Click += delegate
#region 加载页面
{
GC.Collect();
try
{
if (OwnerRow["sFile"].ToString().Length==0) frmCur = this;
else if (frmCur == null || frmCur.IsDisposed)
{
//加载Form窗口所在的程序集
Assembly assDLL = Assembly.LoadFrom(Application.StartupPath + "//" + OwnerRow["sFile"].ToString());
if (assDLL == null)
throw new Exception("找不到文件" + OwnerRow["sFile"].ToString() + "/n请更新程序");
//根据窗体名称生成对应的窗体
frmCur = (Form)assDLL.CreateInstance(OwnerRow["sForm"].ToString());
if (frmCur == null)
throw new Exception("在文件" + OwnerRow["sFile"].ToString() + "中找不到类文件" + OwnerRow["sForm"].ToString() + "/n请更新程序");
frmCur.Tag = OwnerRow["sBoardID"];
frmCur.Text = OwnerRow["sBoardName"].ToString();
}
//如果数据库中有对应的方法,则生成该方法的委托,如果没有则生成窗体显示方法的委托
dlRunMethod RunMethod = null;
if (!OwnerRow.IsNull("sMethod") && OwnerRow["sMethod"].ToString() != "")
RunMethod = (dlRunMethod)System.Delegate.CreateDelegate(typeof(dlRunMethod), frmCur, OwnerRow["sMethod"].ToString());
else if (frmCur != this)
{
RunMethod = frmCur.Show;
frmCur.MdiParent = this;
}
RunMethod();
if (RunMethod.Method.Name == "Show") frmCur.BringToFront();
}
catch (Exception ex)
{
MessageBox.Show("装载文件出错/n" + ex.Message);
}
};
#endregion
}
}
//有子级菜单,加载子级菜单
else
foreach (DataRow oneRow in tabMenu.Rows)
{
//是否是分割符
if (oneRow["sBoardName"].ToString() == "-" && OwnerMenu == null) continue;
else if (oneRow["sBoardName"].ToString() == "-") OwnerMenu.DropDownItems.Add("-");
else
{
ToolStripDropDownItem menuNew = new ToolStripMenuItem(oneRow["sBoardName"].ToString());
menuNew.Tag = oneRow["sBoardID"].ToString();
(OwnerMenu == null ? menuMain.Items : OwnerMenu.DropDownItems).AddRange(new ToolStripItem[] { menuNew });
//递归调用
InstallMenus(menuNew, oneRow);
}
}
}
整个功能实现起来都是比较简单的,唯一有些疑惑的地方可能就是那个sMethod的使用。举个例子来说明。很多软件中都有”退出系统“整个功能,整个功能菜单是只有整个功能而没有界面,和前面的菜单不同。也就是这个菜单是调用的一个方法,比如我们命名为Loginout:
private void Loginout()
{
Application.Exit();
}
如果菜单不是动态生成的,点击菜单的时候调用这个方法就行了。但是菜单是动态生成,也就是这个方法要动态调用。其在数据库中的数据为:
sBoardID sBoardName sFile sForm sMethod
009 重新登录 Loginout