MFC界面编程基础(08):菜单(一)

上一篇:MFC界面编程基础(07):文本编程 下一篇:MFC界面编程基础(09):菜单(二)

菜单栏、工具栏和状态栏是组成Windows程序图形界面的三个主要元素。大多数Windows程序都提供了菜单,作为用于与应用程序之间交互的一种途径。本节主要介绍与菜单相关的编程知识。

菜单命令响应函数

创建一个MFC单文档工程并运行该程序,对应这个新创建的程序来说,MFC已经帮我们创建了一个菜单,并完成了一些菜单功能。,例如 【文件】 菜单下的 【打开】菜单命令,即可弹出打开文件对话框。
在VS中打开资源视图选型卡,可以看到Menu下有一个IDR_MAINFRAME的菜单资源,它就是我们应用程序界面看到的菜单。

MFC界面编程基础(08):菜单(一)_第1张图片
VS为我们提供了一个所见即所得的资源编辑器,如果我们要为应用程序添加自己的菜单项,可以直接在这个菜单中添加,程序运行之后就能看到自己添加的菜单项。
MFC界面编程基础(08):菜单(一)_第2张图片
当选中菜单项相应的就会在属性面板中显示出该菜单项的所用属性,也可以在次面板中对菜单项的属性做相应的修改。
MFC界面编程基础(08):菜单(一)_第3张图片
在MFC中,把设置为Popup类型的菜单称为弹出菜单,一般默认顶层菜单为弹出式菜单,这种菜单不能响应命令,它的ID也是无法被编辑的,如果把Popup设置为False该菜单就不是弹出菜单,而成为一个菜单项,它的ID也能够被编辑了。MFC中都是采用大写字母来标识资源ID号的
在菜单编辑器中给应用程序添加新的菜单项,然后为其添加消息响应函数。在新添加的菜单项上点鼠标右键,弹出菜单中选择 添加事件处理程序(A)…
MFC界面编程基础(08):菜单(一)_第4张图片
弹出如下对话框:
MFC界面编程基础(08):菜单(一)_第5张图片
我们打算在框架类中响应这个菜单命令,选择CMainFrame类,消息类型选择COMMAND,添加编辑,在框架类中会自动添加该菜单命令的响应函数。
MFC界面编程基础(08):菜单(一)_第6张图片
运行程序,单击新添加的MyTest菜单项,会弹出一个消息框,说明OnMyTest函数被调用了。

菜单命令的路由

下面我们研究一下程序中的各个类对菜单命令的响应顺序,是不是菜单命令只有框架类才能捕获呢?
按照上面讲到的方法依次为CMainFrame之外的其他类,视图类、文档类和应用程序类中添加菜单命令响应函数。
MFC界面编程基础(08):菜单(一)_第7张图片
因为CMenuApp和CMenuDoc类都不是从CWnd类派生出来的,所有他们没有MessageBox成员函数,我们可以使用全局的MessageBox函数,或者使用应用程序框架的函数AfxMessageBox,这里我们使用的后者。
添加完成之后,执行程序,单击新添加的菜单项MyTest,弹出一个对话框
MFC界面编程基础(08):菜单(一)_第8张图片
这就是说视类最先响应了这个菜单命令。关闭这个提示框在没有其他信息显示,说明其他几个菜单命令响应函数没有起作用。
下面,我们将视图类的OnMyTest响应函数删除,再次运行程序,我们会发现是文档类做出了响应。后面依次删除、运行。 实验发现:响应【MyTest】菜单项命令的顺序是:视图类、文档类、框架类,最后才是应用程序类。
菜单命令消息路由的具体过程:当点击某个菜单项时,最先接收到这个菜单命令消息的是框架类。框架类将把接收到的这个消息交给它的子窗口,即视图类,由视类首先进行处理。视类首先根据命令消息映射机制查找自身是否对此消息进行了响应,如果响应了,就调用相应响应函数对这个消息进行处理,消息路由过程结束;如果视类没有对此命令消息做出响应,就交由文档类,文档类同样查找自身是否对这个菜单命令进行了响应,如果响应了,就由文档类的命令消息响应函数进行处理,路由过程结束。如果文档类也未做出响应,就把这个命令消息交还给视类,后者又把该消息交还给框架类。框架类查看自己是否对这个命令消息进行了响应,如果它也没有做出响应,就把这个菜单命令消息交给应用程序类,由后者来进行处理。

Windows消息的分类:

  • 标准消息
    除WM_COMMAND之外,所有WM_开头的消息都是标准消息。从CWnd类派生的类,都可以接受这类消息。
  • 命令消息
    来自菜单,加速键或工具栏按钮的消息。这类消息都以WM_COMMAND形式呈现。从CCmdTarget派生的类,都可以接收这类消息。
  • 通告消息
    由控件产生的消息,例如按钮的单击,列表框的选择都会产生这类消息,目的是为了向其父窗口通知事件的发生。这类消息也是以WM_COMMAND形式呈现的。从CCmdTarget派生的类,都可以接收这类消息。
    CWnd类派生于CCmdTarget类。也就是说,凡是从CWnd派生的类,他们既可以接收标准消息,也可以接收命令消息和通告消息。而对于那些从CCmdTarget派生的类则只能接收命令消息和通告消息,不能接收标准消息。

标记菜单

我们想实现这样的功能:在【文件】子菜单中的【新建】菜单项上添加一个标记(√
)。因为程序的主菜单属于框架窗口,所以需要在框架类窗口创建完成之后再去访问菜单对象。可以在框架类的OnCreate函数的最后(但一定要在return语句之前)添加实现这个功能的代码。

首先要获得程序的菜单栏,也就是要在框架窗口中获得指向菜单栏的指针,这可以通过CWnd的成员函数:GetMenu来实现,函数声明如下:
CMenu* GetMenu() const;
该函数返回一个指向CMenu类对象的指针。CMenu类是一个MFC类,提供了一些与菜单操作有关的成员函数,其中就有获取一个菜单的子菜单功能的成员函数GetSubMenu函数。函数原型如下:
CMenu* GetSubMenu(int nPos) const;
这个函数的参数nPos指定了子菜单的索引号。
为了设置一个标记菜单,需要使用CMenu类的CheckMenuItem这个函数,该函数的功能就是为菜单项添加一个标记,或者移除菜单项的标记。该函数声明如下:
UINT CheckMenuItem( UINT nIDCheckItem, UINT nCheck );
MFC界面编程基础(08):菜单(一)_第9张图片

默认菜单项

有些应用程序的子菜单下有一个菜单项是以粗体形式显示的,以这种形式显示的就是该子菜单的默认菜单项。 要实现的功能:将【文件】子菜单下的【新建】菜单项设置为该子菜单的默认菜单项。为了实现这种菜单项,可以利用CMenu类的SetDefaultItem成员函数来完成。这个函数的声明形式如下所示:
BOOL SetDefaultItem(UINT uItem,BOOL fByPos = FALSE);
默认菜单项的代码实现有两种方式:

  • 利用位置索引的方式来实现,这时,SetDefaultItem函数的第二个参数应该设置为TRUE;
  • 利用菜单项标识的方式来实现,这时,SetDefaultItem函数的第二个参数应该设置为FALSE。
    MFC界面编程基础(08):菜单(一)_第10张图片

注意:一个子菜单只能有一个默认菜单项。

分隔栏

要实现的功能:在子菜单中添加分隔栏。在新添加的子菜单的菜单项的属性页中进行修改,具体如下
选择菜单项,在菜单项的属性窗口中【杂项】的【Separator】设置为True,该菜单项会自动变成一个分隔符。

MFC界面编程基础(08):菜单(一)_第11张图片

注意:分隔栏在子菜单中是占据索引位置的。

禁用菜单项

实现功能:屏蔽某一子菜单下某个菜单项的功能。这里我们将要禁用【文件】子菜单下的【打开】菜单项的功能。 利用CMenu类的成员函数:EnableMenuItem来完成。该函数的作用是设置菜单项的状态:能够使用、禁用或变灰显示。其声明形式如下所示:
UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable );
返回值:
返回以前的状态(MF_DISABLED, MF_ENABLED, 或MF_GRAYED),如果无效,则返回-1。
参数:

  • nIDEnableItem 指定由nEnable决定的将要有效的菜单项。该参数既可以指定弹出菜单项,也可以指定标准菜单项。
  • nEnable 指定了将要进行的动作。它可以是MF_DISABLED, MF_ENABLED,或MF_GRAYED与 MF_BYCOMMAND或MF_BYPOSITION的组合。这些值通过位与操作进行组合。这些值有下列含义:
  • MF_BYCOMMAND 指定参数给出已存在的菜单项的命令ID号。此为缺省值。
  • MF_BYPOSITION 指定参数给出已存在菜单项的位置。第一项所在的位置是0。
  • MF_DISABLED 使菜单项无效,以便它不能被选择,但不变灰。
  • MF_ENABLED使菜单项有效,以便它能够被选择,并可从变灰的状态中恢复出来。
  • MF_GRAYED 使菜单项无效,以便它不能被选择并同时变灰。
    MFC界面编程基础(08):菜单(一)_第12张图片
    问题:运行后,选择【文件】子菜单下的【新建】菜单项,但是这时仍会出现“打开文件”对话框,这说明【新建】菜单项未被禁用。
    原因是这样的:
    默认情况下,所有菜单项的更新都是由MFC的命令更新机制完成的。如果我们想自己更改菜单项的状态,那就必须先把m_bAutoMenuEnable变量设置为FALSE,之后我们自己对菜单项的状态更新才能起作用。因此,我们就在程序的CMainFrame类构造函数中把m_bAutoMenuEnable这个变量初始化为FALSE
    MFC界面编程基础(08):菜单(一)_第13张图片

移除和装载菜单

实现功能:移除一个菜单。我们利用Cwnd类提供的SetMenu成员函数来实现,该函数的声明形式如下所示:
BOOL SetMenu( CMenu* pMenu );
这个函数有一个CMenu类型指针的参数,它指向一个新菜单对象。如果这个参数值为NULL,则当前菜单就被移除了
MFC界面编程基础(08):菜单(一)_第14张图片
如果想要装载一个菜单资源并显示。我们可将上面程序中移除的菜单在这里再把它显示出来。我们利用CMenu类提供的LoadMenu成员函数来实现:
MFC界面编程基础(08):菜单(一)_第15张图片
该程序先定义了一个菜单对象,Menu实例程序主菜单的资源标识是IDR_MAINFRAME,把这个资源加载到菜单对象中,最后调用SetMenu函数,把程序的菜单设置为刚刚加载的菜单对象,运行会发现程序的菜单又出现了。

问题:
这里定义的CMenu对象:menu是一个局部对象。虽然运行时未出现错误,但在窗口的操作过程中还是会出现一些错误,这都是因为CMenu对象menu是一个局部对象造成的。
解决问题:

  • 将CMenu对象定义为CMainFrame类的一个成员变量;
  • 仍把这个菜单对象定义为局部对象,但在调用SetMenu函数把此对象设置为窗口的菜单之后,立即调用CMenu类的另一个成员函数Detach,以便把菜单句柄与这个菜单对象分离

MFC界面编程基础(08):菜单(一)_第16张图片

MFC菜单命令更新机制

如果要在程序中设置某个菜单项的状态,可以在菜单编辑器中为这个菜单项添加UPDATE_COMMAND_UI消息响应函数,然后在这个函数中进行状态的设置即可。 当程序框架捕获到了CN_UPDATE_COMMAND_UI消息后,最终还是交由该消息的响应函数来处理,我们会发现在这个函数有一个CCmdUI指针类型的参数,利用这个CCmdUI类,可以决定一个菜单项是否可以使用(Enable函数)、是否有标记(SetCheck函数),还可以改变菜单项的文本(SetText函数)。
MFC界面编程基础(08):菜单(一)_第17张图片

实现功能:改变【文件】子菜单下的【保存】菜单项的状态。
MFC界面编程基础(08):菜单(一)_第18张图片
我们可从工具栏上的保存按钮观察代码修改后的结果。
注意:如果要把工具栏上的一个工具按钮与菜单栏中的某个菜单项相关联,只要将它们的ID设置为同一个标识就可以了。

上一篇:MFC界面编程基础(07):文本编程 下一篇:MFC界面编程基础(09):菜单(二)

你可能感兴趣的:(MFC,菜单)