[MFC]Shapes程序(3):响应菜单命令、菜单项的跟新

来源:互联网 发布:vb整除符号 编辑:程序博客网 时间:2024/05/04 01:20

1. 菜单项被击中的消息:

    1) 一般关于菜单项产生最平凡的几个消息莫过于WM_INITMENU、WM_INITMENUPOPUP、WM_MENUSELECT这三个消息;

    2) WM_INITMENU在顶层菜单项被击中时产生,通知应用程序用户已击中一个顶层菜单项,要做好准备了;

    3) WM_INITMENUPOPUP消息会在顶层菜单项被击中后、子菜单被下拉之前产生,你可能会问了,直接在处理WM_INITMENU时下来菜单不就行了,为啥还有等这么一个消息呢?因为可以在这个消息的处理中更新菜单栏UI界面的状态,比如用户将工具栏显示了出来,此时就可以在新弹出的子菜单的“工具栏”选项旁边的复选框中填上一个勾;

    4) WM_SELECTMENU会在子菜单选中加亮条上下移动时产生,用以报告加亮条的最新位置;


2. 菜单命令的消息映射:

    1) 用户最关心的往往是菜单项的WM_COMMAND命令,即某个能产生应用程序命令的菜单项被击中后产生的命令;

    2) 这种消息的参数wParam的低字位保留了该菜单项的命令ID,用户可以根据该ID将此类消息准确地映射到相应的消息响应函数中去;

    3) 消息映射条目:条目名为ON_COMMAND,后面两个参数和一般消息条目不太一样,一般条目的第一个参数是消息ID,第二个参数是映射的消息响应函数名,如果消息ID以及响应函数都是MFC事先定义好的,则宏参数不写,但不过ON_COMMAND的映射是以命令ID为基准的,因此第一个参数是命令ID,第二个参数不变,仍然是响应函数名,例如:

ON_COMMAND(ID_FILE_NEW, OnFileNew)ON_COMMAND(ID_FILE_OPEN, OnFileOpen)

!命令处理函数的名称用户可以随意去,不像其他WM_消息,消息ID和响应函数名都是MFC预定义好的,程序员不能改变,想改变除非修改MFC源码,而命令相应则不一样,首先命令ID是可以用户随意取的,因此MFC对命令响应函数的名称也不作要求,可以随意取,比如:

ON_COMMAND(ID_FILE_NEW, CreateNewFile)
    4) 响应函数原型无参无返回值:afx_msg void OnFileExit();


3. 命令范围——处理ID连续的菜单命令:

    1) 在实际应用中经常会碰到类似如下问题,比如我们的Shapes程序,Color子菜单中会有很多菜单项供选择,包含很多不同的颜色,比如Green、Blue、Red等,如果为每个选项都提供给一个响应函数将会是一个浪费时间浪费代码的事情,比如为ID_COLOR_GREEN提供一个OnColorGreen,为ID_COLOR_BLUE提供一个OnColorBlue,这些响应函数所做的工作完全相同,就是把图形染成被选中的颜色而已;

    2) 在处理如上问题时使用到的快捷省时的技巧就是将这些ID在数值上定义成连续的,然后将这些命令都映射到统一个响应函数中,然后在响应函数中调用CWnd的GetCurrentMessage函数获取被选中的消息ID,消息ID保存在当前消息wParam的低字段中,例如:

// Resource.h...#define ID_COLOR_RED 100#define ID_COLOR_GREEN 101...#define ID_COLOR_BLACK 155// View.cpp...ON_COMMAND(ID_COLOR_RED, OnColor)ON_COMMAND(ID_COLOR_GREEN, OnColor)...ON_COMMAND(ID_COLOR_BLACK, OnColor)...void CView::OnColor(){UINT nColorID = (UINT)LOWORD(GetCurrentMessage()->wParam);m_nCurrentColor = nColorID - ID_COLOR_RED;}
!该方法其实不值得提倡,因为该方法严重依赖wParam参数,而历史上Windows对wParam的含义修改过,一旦意义改变那么你所有的程序都需要修改,并且该方法还是避免不了要大量写ON_COMMAD的消息条目,并且每个条目都占24字节,相当费内存,其实最好的方式使用MFC的ON_COMMAND_RANGE宏,该宏专门用来将一组连续ID映射到同一个响应函数上去;

    3) ON_COMMAND_RANGE:接上例

// 前两个参数指示了连续ID的最小值和最大值ON_COMMAND_RANGE(ID_COLOR_RED, ID_COLOR_BLACK, OnColor)void CView::OnColor(UINT nID) // 被ON_COMMAND_RANGE映射的相应函数会直接得到nID参数{ // 该nID就是被选中的菜单项的命令ID,隐藏了中间获取的过程,不依赖wParam,即使改变也不用修改m_nCurrentColor = nID - ID_COLOR_RED;}


4. 更新菜单项:接上例

    1) 前面说过,菜单项往往能反映应用程序的内部状态和数据,比如通过菜单项的复选框来指示一个东西是否被启用或选中等;

    2) 跟新菜单共有4种方法,它们的效果都相同,并且编写复杂度第一个最低,后面三个比第一种复杂,但是从程序结构性和健壮性上讲,这4中方法依次递增:

         i. 菜单项更新和命令响应放在一起:

void CFrame::OnColor(UINT nID){// 消息处理m_nCurrentColor = nID - ID_COLOR_RED;// 菜单项更新CMenu* pMenu = GetMenu();pMenu->CheckMenuItem(m_nCurrentColor + ID_COLOR_RED, MF_UNCHECKED); // 去掉老的pMenu->CheckMenuItem(nID, MF_CHECKED); // 勾上新的}
!GetMenu:CMenu* CWnd::GetMenu() const;,该函数得到当前窗口的菜单的句柄,一定要是父窗口(最好是框架窗口),子窗口没有菜单;

!CheckMenuItem:UINT CMenu::CheckMenuItem(UINT nIDCheckItem, UINT nCheck);,第一个参数为目标菜单项的ID,第二个参数是设定的复选框状态,主要有两种,一种是MF_CHECKED,表示复选框被选中,第二种是MF_UNCHECKED,表示取消选中,MF_为Menu Flag的缩写;

!该函数返回值是改变前的nCheck状态;

!这种方法有一个严重的缺陷,那就是消息响应函数必须是Frame的,子菜单View中木有菜单,因此只能在Frame中雕也难怪GetMenu,而我们希望所有的消息相应都在View中进行;

         ii. 更新和相应分离:前面讲过,在WM_INITMENUPOPUP的消息处理中更新菜单,这里就一股脑儿在一个OnInitMenuPopup中跟新所有的菜单项

ON_WM_INITMENUPOPUP()void CFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu){if (bSysMenu) {处理系统菜单...}switch (nIndex) {case COLOR_MENU_INDEX:pPopupMenu->CheckMenuItem(...);pPopupMenu->CheckMenuItem(...);...break;case OTHER1_MENU_INDEX:...break;case OTHER2_MENU_INDEX:...break;...default:...break;}}
!这种做法同样不提倡,因为WM_INITMENUPOPUP的相应同样是在Frame中进行的,最好还是放在View中处理;

!该相应是系统预定义的,响应函数的原型就如上定义;

!pPopupMenu:被选中要弹出的子菜单的句柄;

!nIndex:被选中的顶层子菜单在所有子菜单中的编号,MFC对所有的顶层子菜单基于0编号过,该编号用于在相应函数中用作判断选择的是哪个顶层菜单项,该编号以及表示该编号的宏都是MFC自动生成,可以在switch-case中看见,宏名就是"菜单项名_MENU_INDEX",都是大写的;

!bSysMenu:用于表示选中的是否是系统菜单,如果是则为TRUE,否则为FALSE;

!这种做法严重不提倡,是C语言的做法,所有子菜单列表中所有的菜单项都在一个函数内更新,非常难看,代码非常不及安装;

         iii. 响应WM_UPDATE_COMMAND_UI消息:可以使用相应的ON_UPDATE_COMMAND_UI为单个菜单项映射一个更新函数,就好比ON_COMMAND可以为单个菜单项映射一个命令响应函数一样;

ON_UPDATE_COMMAND_UI(ID_COLOR_RED, OnUpdateColor)ON_UPDATE_COMMAND_UI(ID_COLOR_BLUE, OnUpdateColor)...ON_UPDATE_COMMAND_UI(ID_COLOR_BLACK, OnUpdateColor)void CView::OnUpdateColor(CCmdUI* pCmdUI){pCmdUI->SetCheck(m_nCurrentColor == pCmdUI->m_nID - ID_COLOR_RED);}
!在哪儿都可以使用CCmdUI,该类不是专用于某一类型的用户界面的更新,而是用于其它更多更丰富的UI对象界面的更新,包括菜单的一切、工具条以及其他UI对象,并且CCmdUI类在哪儿都可以使用,因此可以在View类里处理Frame的菜单的更新;

!用ON_UPDATE_COMMAND_UI映射的更新函数会得到一个CCmdUI的指针,它刚好指向映射中第一个参数指定的那个UI对象,可以通过CCmdUI的成员变量m_nID来判断当前处理的是哪个UI对象;

!其实CCmdUI的意思很好理解,就是command的UI界面,即菜单命令的菜单项所对应的显示界面嘛;

               a. CCmdUI的两个重要的成员变量:m_nID表示其所指向的UI对象的ID宏,m_nIndex表示其所指向的UI对象的索引值(是一个从0开始计的,之前讲过);

               b. CCmdUI的4大重要函数:

// 使菜单项有效或无效,TRUE有效FALSE无效virtual void Enable(BOOL bOn = TRUE);// 选中或取消选中复选框,1选中,0取消选中// 注意该函数之人0和1,而不是0和非0,因为2也有意义// 但不过2这个值暂时用不到virtual void SetCheck(int nCheck = 1);// 添加或删除单选标记,TRUE添加,FALSE删除virtual void SetRadio(BOOL bOn = TRUE);// 改变菜单项的文本virtual void SetText(LPCTSTR lpszText);
         iv. 范围更新:使用ON_UPDATE_COMMAND_UI_RANGE,其作用就和ON_COMMAND_RANGE相对于ON_COMMAND一样

ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_RED, ID_COLOR_BLACK, OnUpdateColorUI)void CView::OnUpdateColorUI(CCmdUI* pCmdUI){pCmdUI->SetCheck(pCmdUI->m_nID - ID_COLOR_RED == m_nCurrentColor);}

!推荐优先使用这种方法更新菜单UI,结构上健壮,同时又能让更新和相应命令分离;

0 0
原创粉丝点击