五. 菜单编程--Windows编程课程学习笔记

原文地址: https://blog.csdn.net/ling_xiao007/article/details/51721119

5.1 菜单命令响应函数

在资源视图中,Menu下的IDR_MAINFRAME是一个默认菜单资源,可以在此基础上继续添加菜单项。

MFC中,设置为Pop-up类型的菜单称为弹出式菜单,VC++默认顶层菜单为弹出式菜单,这种菜单不能响应命令。将菜单的属性对话框中的Pop-up选项去掉,该菜单成为一个菜单项,对应有一个ID号,可以响应命令。

添加一个新的菜单项,将Popup改成false,ID号为ID_TEST。利用类向导的方法为其在主框架类添加响应函数(在命令选项卡,找ID_TEST对象)。添加一个消息框弹出显示”MainFrame Clicked”的命令。

void CMainFrame::OnTest()
{
    // TODO: 在此添加命令处理程序代码
    MessageBox("MainFrame Clicked");
}

5.2 菜单命令的路由

5.2.1程序类对菜单命令的相应顺序

响应Test菜单项命令的顺序依次是:视类、文档类、框架类、应用程序类。

5.2.2 Windows消息的分类

1)标准消息

除WM_COMMAND之外,所有以WM_开头的消息。从CWnd派生的类,都可以接收到这类消息。

2)命令消息

来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。从CCmdTarget派生的类,都可以接收到这类消息。

3)通告消息

由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。从CCmdTarget派生的类,都可以接收到这类消息。

CWnd派生于CCmdTarget类。凡是从CWnd派生的类,它们既可以接受标准消息,也可以接受命令消息和通告消息。CCmdTarget派生的类,只能接受命令消息和通告消息。

5.2.3 菜单命令的路由

1)当点击某菜单项时,最先接收到这个菜单命令消息的是框架类。

2)框架类把接收到的这个消息传给它的子窗口,即视类。视类根据命令消息映射机制查找自身是否对这个消息进行了响应,如果响应了,则调用自身相应响应函数。

3)如果视类没有对此命令消息作出响应,就交由文档类,文档类同样查找自身是否这个消息进行了响应,如果响应了,则调用自身相应响应函数。

4)如果文档类也未做出响应,就把这个命令消息交还给视类,后者再交还给框架类。

5)框架类查看自己是否对这个命令消息进行了响应,如果它也没有相应,就把这个菜单命令消息交给应用程序类,由后者来处理。

5.3基本菜单操作

子菜单和菜单项的概念类比于楼层和房间号。

5.3.1 标记菜单

为【文件】子菜单的【新建】菜单项添加标记。

1)获得【文件】子菜单的【新建】菜单项。

①首先获得菜单栏GetMenu(CWnd成员函数);

②获得子菜单GetSubMenu(CMenu成员函数),有一个参数nPos指定了子菜单的索引号;

③UNIT CheckMenuItem(UINT nIDCheckItem, UINT nCheck)可以为菜单项添加(移除)一个标。

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
…
    // 根据位置,计算索引时分隔符也算
    GetMenu()->GetSubMenu(0)->CheckMenuItem(0,MF_BYPOSITION | MF_CHECKED);
    //ID号
    GetMenu()->GetSubMenu(0)->CheckMenuItem(ID_FILE_NEW, MF_CHECKED);
    return 0;
}

5.3.2 默认菜单项

子菜单下有一个菜单项是粗体形式显示的,就是该子菜单的默认菜单项。可以用CMenu类的SetDefaultItem函数实现。一个子菜单只能有一个默认菜单项。

    BOOLSetDefaultItem(UINT uItem, Bool fByPos = FALSE);
    GetMenu()->GetSubMenu(0)->SetDefaultItem(1,TRUE);
    GetMenu()->GetSubMenu(0)->SetDefaultItem(ID_FILE_OPEN);

5.3.3 图形标记菜单

子菜单下用图形标记。可以用CMenu类的SetMenuItemBitmaps函数实现。nPosition由nFlag决定,MF_BYCOMMAND则第一个为菜单项标识,MF_BYPOSITION则为索引。pBmpUnchecked取消时位图,pBmpChecked选定时位图。位图可以为私有成员变量,也可以局部变量Detach。

    UNITSetMenuItemBitmaps(UINT nPosition, UINT nFlag, 
                            const CBitmap* pBmpUnchecked,
                            const CBitmap* pBmpChecked);
    CBitmap bmp1, bmp2;
    bmp1.LoadBitmap(IDB_BITMAP1);
    bmp2.LoadBitmap(IDB_BITMAP2);
    GetMenu()->GetSubMenu(0)->SetMenuItemBitmaps(0,MF_BYPOSITION, &bmp1, &bmp2);
    bmp1.Detach();
    bmp2.Detach();

可以用GetSystemMetrics函数获得图形标记菜单上显示的位图尺寸。因为VC6.0++只能显示局部图形,而VS2015中可以根据图形尺寸调整显示。

    CString str;
    str.Format("x = %d, y = %d", GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK));
    MessageBox(str);

5.3.4 禁用菜单项

实现使用禁用或变灰显示。nIDEnableItem由nEnable决定。菜单的禁用状态和变灰状态是不同的。通常把MF_GRAYED和MF_DISABLED这两个标志放在一起使用。该函数要生效,必须在CMainFrame类的构造函数中把成员变量m_bAutoMenuEnable设置为FALSE,不会对ON_UPDATE_COMMAND_UI和ON_COMMAND消息进行响应了,但同时【编辑】子菜单不再以灰色显示。要使用菜单命令更新机制,则该变量应设置为TRUE(缺省值)。

    UNIT EnableMenuItem(UINT nIDEnableItem, UINTnEnable)
    GetMenu()->GetSubMenu(0)->EnableMenuItem(3,MF_BYPOSITION | MF_GRAYED | MF_DISABLED);

5.3.5 移除和装载菜单

移除一个菜单可以利用CMenu类的SetMenu函数。BOOL SetMenu(CMenu * pMenu);加载菜单可以利用CMenu类的LoadMenu函数。如果CMenu对象是一个临时对象,则在加载完成之后必须加上menu.Detach()。Detach会把菜单句柄与这个菜单对象分离,这样,当这个局部对象的生命周期结束时,它不会去销毁一个它不再具有拥有权的菜单资源。

    SetMenu(NULL);
 
    CMenu menu;
    menu.LoadMenu(IDR_MAINFRAME);
    SetMenu(&menu);
    menu.Detach();

5.3.6 MFC菜单命令更新机制

菜单项状态的维护是依赖于CN_UPDATE_COMMAND_UI消息,谁捕获CN_UPDATE_COMMAND_UI消息,MFC就在其中创建一个CCmdUI对象。我们可以通过ClassWizard在消息映射中添加ON_UPDATE_COMMAND_UI宏来捕获CN_UPDATE_COMMAND_UI消息。

在后台所做的工作是:当显示菜单的时候,操作系统发出WM_INITMENUPOPUP消息,然后由MFC的基类如CFrameWnd接管。它创建一个CCmdUI对象,并与第一个菜单项相关联,调用对象的一个成员函数DoUpdate()。这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有指向CCmdUI对象的指针。同一个CCmdUI对象就设置为与第二个菜单项相关联,这样顺序进行,直到完成所有菜单项。

更新命令UI处理程序仅应用于弹出式菜单项上的项目(有ID号),不能应用于顶层菜单项目(无ID号)。

工具栏上的一个工具按钮与菜单栏中的某个菜单项相关联,只要他们的ID设置为一个标识就可以。菜单栏和工具按钮的位置索引计算方式不同,最好采用菜单项标识或工具栏按钮标识的方式来进行设置。

5.3.7 快捷菜单

  1. 为Menu程序增加一个新的菜单资源。在ResouceView上的Menu分支上单击鼠标右件,选择“Insert Menu”命令,为这个菜单资源添加菜单项。由于在显示快捷菜单时顶级菜单不出现,所以可以给它设置任意的文本。

2)给视类添加WM_RBUTTONDOWN消息响应函数。加载菜单资源到CMenu对象

3)调用TrackPopupMenu函数。BOOL TrackPopupMenu(NUINT nFlags, int x, int y, CWnd* pWnd, LPSCECTlpRect = NULL); nFlags为指定菜单显示位置,xy为坐标,pWnd指定拥有者,lpRect指定矩形区域,若在此区域点击不显示。将鼠标点的客户去坐标转换为屏幕坐标。ClientToScreen。

void CMyMFCAppView::OnRButtonDown(UINT nFlags, CPoint point)
{
    //TODO: 在此添加消息处理程序代码和/或调用默认值
    CMenu menu;
    menu.LoadMenu(IDR_MENU1);
    CMenu* pPopup = menu.GetSubMenu(0);
    ClientToScreen(&point);
    pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
 
    CView::OnRButtonDown(nFlags, point);
}

4)利用类向导添加响应函数。选择COMMAND消息。在视类和主框架类分别响应。可以看到出现View show字符串的消息框。对于快捷菜单,如果将其拥有者窗口设置为框架类窗口,则框架类窗口才能有机会获得对该快捷菜单中的菜单项的命令响应,否则,就只能有视类窗口作出响应。

void CMyMFCAppView::OnShow()
{
    //TODO: 在此添加命令处理程序代码
    MessageBox("Viewshow!");
}
 
void CMainFrame::OnShow()
{
    //TODO: 在此添加命令处理程序代码
    MessageBox("Frameshow!");
}

5.4 动态菜单操作

5.4.1 添加菜单项目

1)添加顶层菜单:创建一个菜单对象, CreateMenu,之后添加一个菜单项目AppendMenu,BOOL AppendMenu (UINT nFlags, UINT_PTRnIDNewItem = 0, LPCTSTR lpszNewItem = NULL) nFlags状态信息,nIDNewItem取决于第一个,MF_POPUP则为一个顶层句柄,lpszNewItem取决于第一个。因为CMenu定义了一个局部变量,所以要Detach。

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    …
    CMenumy_menu;
    my_menu.CreateMenu();
    GetMenu()->AppendMenu(MF_POPUP(UINT)my_menu.m_hMenu,"my_menu");DrawMenuBar();
    my_menu.Detach();
    …
}

2)添加顶层菜单下的菜单项:

GetMenu()->GetSubMenu(0)->AppendMenu(MF_POPUP, (UINT)my_menu.m_hMenu,"hello");

5.4.2 插入菜单项目

1)插入顶级菜单:InsertMenu函数实现。BOOL InsertMenu(UINT nPosition, UINT nFlags, UINT_PTR nIDNewItem = 0,LPCTSTR lpszNewItem = NULL);

    CMenu my_menu;
    my_menu.CreateMenu();
    //GetMenu()->AppendMenu(MF_POPUP,(UINT)my_menu.m_hMenu, "my_menu");
    GetMenu()->InsertMenu(2, MF_BYPOSITION | MF_POPUP, (UINT)my_menu.m_hMenu,"my_menu");
    my_menu.Detach();

2)插入顶级菜单下的菜单项

    GetMenu()->GetSubMenu(0)->InsertMenu(0, MF_STRING | MF_BYPOSITION,777, "Hello");

5.4.3 删除菜单

1)删除顶级菜单:BOOL DeleteMenu(UINTnPosition, UINT nFlags);

    GetMenu()->DeleteMenu(1, MF_BYPOSITION);
    DrawMenuBar();

2)删除顶级菜单下的菜单项:

    GetMenu()->GetSubMenu(0)->DeleteMenu(0,MF_BYPOSITION);
    GetMenu()->GetSubMenu(0)->DeleteMenu(ID_FILE_NEW, MF_BYCOMMAND);

5.4.4 动态添加的菜单项的命令响应

遵循MFC的消息映射机制,需要手动添加三处代码来实现命令消息的响应。可先利用ClassWizard对程序中某个已有的静态菜单项添加命令消息响应,然后参照ClassWizard在程序中为其添加的内容,来完成动态菜单添加命令响应。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容