问题描述:
是将MenuStrip中的所有DropDownItems列出来,供用户选择,被选中的项将在ToolStrip被显示出来,并且可以使用与其对应的DropDownItem的事件.
其工作流程可以分解如下图所示:
图1.1流程图
实际上这个图所描述的正是我们上面说的一样,只是换作图形表示而已,但这样一来程序的结构就变得清晰起来了。
实际上这个图所描述的正是我们上面说的一样,只是换作图形表示而已,但这样一来程序的结构就变得清晰起来了。可以看出,整个问题实际上被分解成了三个部分,即遍历菜单栏所有可用的DropDownItems,提供用户的选择界面,和为工具栏动态添Items及其事件。提供用户的选择界面这部分内容没什么技术含量,可以用任何一种你喜欢的方式实现,没什么好说的。下面我将重点说一下遍历菜单栏所有可用的DropDownItems,和为工具栏动态添Items及其事件这两部分。
一、 遍历菜单栏所有可用的DropDownItems
菜单栏的结构实际上是典型的“森林”,而每一个下拉菜单(DropDownItem)都是一棵树。面对这种问题最好的解决方式就是递归。很多人提到递归就头大,其实对于了解递归的人来说递归很简单。尤其是树的遍历就更加的简单了,它有固有的模式,一般先对结束条件进行判断,然后递归遍历各个子树,就这么简单。对于本题DropDownItem的函数可以这样来写:
private ToolStripMenuItem Items(ToolStripMenuItem tsItem)
{ //结束条件判定
if (tsItem.DropDownItems.Count <= 0) return tsItem;
else
{ //遍历各子树
foreach (ToolStripMenuItem mItem in tsItem.DropDownItems)
{ //递归遍历mItem子树
Items(mItem);
}
}
return null;
}
函数首先判断节点(就是一个DropDownItem)是否含有子节点,如果它不含有子节点就return它(这就是结束条件判断)。我们这样做完全是因为我们菜单使用习惯(所有的菜单功能都是在叶子节点实现功能的),当然我们也可以将非叶子节点也return回去。但是最好不要这么干,因为它会令我们产生很多困扰(不信邪的朋友可以试试)。下一步再分别遍历该节点的每一个子树
上面是遍历某个DropDownItem和它的子节点,下面遍历整个菜单栏:
……
//这是遍历菜单栏,可以放在任何你需要的地方,
//注: menuStrip1是我菜单栏的名字
foreach (ToolStripMenuItem mItem in this.menuStrip1.Items)
{ //递归遍历各个DropDownItem
Items(mItem);
}
//
……
上面就是遍历菜单的全部代码。这当然还没完,我们需要将遍历菜单得到的各个菜单项存入容器,以备下面的程序使用。什么容器都可以,而我选择了List<>。要实现这个功能:
首先,我们需要增加一个容器 lMenuItems
……
//增加一个全局变量,它是一个容器
List<ToolStripMenuItem> lMenuItems;
……
然后,在遍历菜单栏程序段中添加代码
//这是遍历菜单栏,可以放在任何你需要的地方,
//menuStrip1是我菜单栏的名字
lMenuItems = new List<ToolStripMenuItem>();
foreach (ToolStripMenuItem mItem in this.menuStrip1.Items)
{ //递归遍历各个DropDownItem
Items(mItem);
}
//
……
将DropDownItem的递归遍历函数修改成下面这个样子
private ToolStripMenuItem Items(ToolStripMenuItem tsItem)
{ //结束条件判定
if (tsItem.DropDownItems.Count <= 0) return tsItem;
else
{ //遍历各子树
ToolStripMenuItem value = null;//临时变量
foreach (ToolStripMenuItem mItem in tsItem.DropDownItems)
{ //递归遍历mItem子树
value = Items(mItem);
if (value != null && lMenuItems != null)
lMenuItems.Add(value);
}
}
return null;
}
好,初战告捷。跳过如何提供用户的选择界面部分不提,直接进入重点。
二、 为工具栏动态添Items及其事件
给工具栏动态添加Items其实是一件非常简单的事,就像有些人说的new一个,再Add()进去就搞定了。关键的问题不在这里,如何使各个工具栏的Item的事件可以与DropDownItem的事件一一对应才是问题的关键。各个DropDownItem都可以拥有自己的事件,各个事件的名称不同,而且事件是不能直接拿事件赋值的,如果无法实现事件的动态赋值,菜单栏动态添加也就没戏了。如果没有好多解决方法,程序开发到这里只能是僵局。
1. 关于事件的调整
首先事件是可以调用事件的,并且几个控件可以共用一个事件。这就为问题的解决提供了可能性。让所有的可用菜单项都使用一个事件,或者说让这个事件统领其他的菜单事件。我们新建一个事件,并将这个事件命名为Menu_Click,它来统领整个Menu的所有Click事件:
private void Menu_Click(object sender, EventArgs e)
{ …… }
第二步,将原来所有DropDownItems的事件(菜单项事件)都替换成Menu_Click。
小知识: 事件参数中的sender事实上就是发出事件的控件(或源),但因为它的类型是object,所有在使用前要对其进行强制类型转换。比如:有一个sender的实际类型是ToolStripItem,我需要获得sender的Name属性值就需要: string sender_Name = ((ToolStripItem)sender).Name; |
应用上面特点,在事件处理函数Menu_Click中就不难使用switch或者if/else来区分事件的来源,并分别调用他们自己的事件处理函数了。
2. 动态添加
解决了事件的问题,工具栏的动态添加实际上就是一句话的事
toolStrip1.Items.Add(new ToolStripButton(
<某DropDownItem>.Text, //DropDownItem显示的文字
<某DropDownItem>.Image, //DropDownItem图标
new System.EventHandler(Menu_Click), //DropDownItem事件
<某DropDownItem>.Name //DropDownItem名称
));
下面是一个简单的实例:(请将代码粘贴到你新建项目中对应得位置)
/*******************/
/*Form1.Designer.cs*/
/*******************/
namespace dynamicToolbar
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;