- ·上一篇内容:基于C#的Socket开发入门教程
- ·下一篇内容:在C#中实现MFC中CRectTracker橡皮区矩形类的方法
在C#中使用组件实现office2003风格的菜单
UninitMenuItem的定义与此类似,其详细定义如下:
private void UninitMenuItem(Menu mi)
{
foreach ( MenuItem m in mi.MenuItems )
{
// 取消对MesaureItem事件处理的订阅
m.MeasureItem -=
new System.Windows.Forms.MeasureItemEventHandler(this.menuItem_MeasureItem);
// 取消对DrawItem事件处理的订阅
m.DrawItem -=
new System.Windows.Forms.DrawItemEventHandler(this.menuItem_DrawItem);
// 将OwnerDraw属性设置为false
m.OwnerDraw = false;
// 递归调用本函数将所有的子菜单项进行相同的操作
UninitMenuItem(m);
}
}
menuItem_MeasureItem事件处理方法定义如下:
private void menuItem_MeasureItem(object sender, System.Windows.Forms.MeasureItemEventArgs e)
{
//先取得要进行消息处理的MenuItem对象
MenuItem item = (MenuItem) sender;
if ( mi.Text == "-" ) { e.ItemHeight = 7;}
else {
// 获取item的文字宽度
SizeF miSize = e.Graphics.MeasureString(mi.Text, Globals.menuFont);
int scWidth = 0;
// 获取快捷键的宽度
if ( mi.Shortcut != Shortcut.None ) {
SizeF scSize = e.Graphics.MeasureString(mi.Shortcut.ToString(), Globals.menuFont);
scWidth = Convert.ToInt32(scSize.Width);
}
// 设置item的边界
int miHeight = Convert.ToInt32(miSize.Height) + 7;
if (miHeight < 25) miHeight = Globals.MIN_MENU_HEIGHT;
e.ItemHeight = miHeight;
e.ItemWidth = Convert.ToInt32(miSize.Width) + scWidth + (Globals.PIC_AREA_SIZE * 2);
}
}
menuItem_DrawItem的实现如下:
private void menuItem_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
MenuItemDrawing.DrawMenuItem(e, (MenuItem) sender);
}
该函数只是简单地调用了类MenuItemDrawing中的静态方法DrawMenuItem。
Start中为主菜单的子菜单添加事件处理的mainMenuItem_MeasureItem和mainMenuItem_DrawItem的定义如下:
private void mainMenuItem_MeasureItem(object sender, System.Windows.Forms.MeasureItemEventArgs e)
{
MenuItem mi = (MenuItem) sender;//获得菜单项对象
SizeF miSize = e.Graphics.MeasureString(mi.Text, Globals.menuFont);
//由于顶级菜单(如文件菜单)无快捷键和图标,所以绘制的宽度为文字的宽度。
e.ItemWidth = Convert.ToInt32(miSize.Width);
}
private void mainMenuItem_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
MainMenuItemDrawing.DrawMenuItem(e, (MenuItem) sender);
}
该方法只是调用了类MainMenuItemDrawing中的静态方法DrawMenuItem进行菜单绘制。
其它方法如AddPicture和GetItemPicture的定义如下:
public void AddPicture(MenuItem mi, int index)
{
//将菜单项的句柄转化为字符串与图标的索引一一对应添加到picDetails集合中。
picDetails.Add(mi.Handle.ToString(), index.ToString());
}
public static Bitmap GetItemPicture(MenuItem mi)
{
if ( _imageList == null )
return null;
//将菜单项的句柄作为键查找该键对应的值,返回值为图标索引
string [] picIndex = picDetails.GetValues(mi.Handle.ToString());
if ( picIndex == null )
return null;
else
//根据索引取出位图对象并返回
return (Bitmap)_imageList.Images[Convert.ToInt32(picIndex[0])];
}
实现如下(由于代码较长,这里仅给出解释,详细代码请在本文开始和结尾处下载):
public class MainMenuItemDrawing
{
//静态方法,实现菜单项的绘制
public static void DrawMenuItem(System.Windows.Forms.DrawItemEventArgs e, MenuItem mi)
{
首先检查menuItem的状态,如为鼠标悬浮在其上的状态,则调用DrawHoverRect绘制并填充悬浮矩形;
如为选定态,则调用DrawSelectionRect绘制并填充相应的选定态时的矩形;
如两者都不是,则用控件的填充色绘制并填充矩形。
最后利用e.Graphics.DrawString方法绘制菜单文字。
}
……
}
类MenuItemDrawing负责主菜单的子菜单和上下文菜单的绘制。
public class MenuItemDrawing
{
//静态方法,实现菜单项的绘制
public static void DrawMenuItem(System.Windows.Forms.DrawItemEventArgs e, MenuItem mi)
{
检查菜单顶是否被选中,如被选中,则调用DrawSelectionRect绘制并填充选中后的矩形,
否则只用背景色绘制空白区域并调用DrawPictureArea绘制图片区域。
调用DrawCheckBox绘制复合框如果该菜单项被选中。
调用DrawMenuText绘制菜单项文字。最后调用DrawItemPicture绘制图标。
}
……
}
结束语:
由于.net中使用了GDI+,所以组件的绘制工作比以前在MFC或者Win32API模式下绘制要容易地多。组件的开发最重要的一点就是当菜单项被置为自绘方式后,用户需要激活两个事件来定制菜单的显示。第一个事件对应Win32的WM_MEASUREITEM消息。窗口收到这个消息时,它就会触发一个 MeasureItem 事件给所有的自绘 MenuItem 对象。这个事件代理(Delegate)是一个名为MeasureItemEventHandler的类,与此事件相关的信息都被存储在一个MeasureItemEventArgs 对象中并被传递到事件处理函数(文中为mainMenuItem_MeasureItem或者menuItem_MeasureItem)。第二个事件与 Win32 的WM_DRAWITEM消息对应,并给每个注册了的事件处理函数传递一个 DrawItemEventArgs 对象。这个事件代理是一个名为DrawItemEventHandler的类。个人认为组件实现的难点和重点就是在两事件处理函数中根据菜单的不同状态所要进行的不同绘制工作。类MainMenuItemDrawing主要负责主菜单的一级子菜单(如常见的文件菜单)的绘制。
本文用到的组件源码及测试程序下载地址
微信搜索“优雅的代码”关注本站的公众号,或直接使用微信扫描下面二维码关注本站公众号,以获取最新内容。
个人成长离不开各位的关注,你的关注就是我继续前行的动力。