MFC中用户界面元素更新原理(UPDATE_COMMAND_UI)


大家在编程的过程中一定遇到过这种情况:需要根据某个变量的值来设定菜单项是否被选中,设置工具栏按钮是否被按下或者在状态栏中显示一些信息。MFC提供了一种机制来帮助我们完成这项工作:只要用ClassWizard给相应的菜单项或者工具栏按钮添加一个UPDATE_COMMAND_UI处理函数,在其中用CcmdUI::SetCheck等函数来设置这些用户界面元素的状态就可以了。但是MFC是怎么实现这个功能的呢?     首先让我们来看看菜单状态更新的实现方法。首先要知道,当你点现了一个有子菜单的菜单项时(比如菜单栏上的“文件”),系统会向拥有这个菜单的窗口发送一个WM_INITMENUPOPUP,下面是MFC对这个消息的默认处理:void CFrameWnd::OnInitMenuPopup(CMenu* pMenu, UINT nIndex, BOOL bSysMenu){     //为了说明问题,我省略了很多代码        CCmdUI state;      state.m_pMenu = pMenu;                   state.m_nIndexMax = pMenu->GetMenuItemCount();      for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax;state.m_nIndex++){             state.m_nID = pMenu->GetMenuItemID(state.m_nIndex);             if (state.m_nID == (UINT)-1)             {               //m_nID==-1表示它下面还有popup menu(就那种带右箭头的菜单项),               //它是不会自动deisable的                                }             else             {                  state.m_pSubMenu = NULL;                  state.DoUpdate(this, m_bAutoMenuEnable && state.m_nID < 0xF000);             } } 下面是CCmdUI::DoUpdate的代码: BOOL CCmdUI::DoUpdate(CCmdTarget* pTarget, BOOL bDisableIfNoHndler){      m_bEnableChanged = FALSE; BOOL bResult=pTarget->OnCmdMsg(m_nID,CN_UPDATE_COMMAND_UI, this, NULL);      if (bDisableIfNoHndler && !m_bEnableChanged){             AFX_CMDHANDLERINFO info;             info.pTarget = NULL;             BOOL bHandler = pTarget->OnCmdMsg(m_nID, CN_COMMAND, this, &info);             Enable(bHandler);      }       return bResult; } DoUpdate的流程就是:先向你的菜单项发一个CN_UPDATE_COMMAND_UI命令消息,让你的菜单项来进行显示前的更新,这就是你在classwizard中可以看到的UPDATE_COMMADN_UI消息,你加的处理函数就是在这个时候被调用的。如果你处理了CN_UPDATE_COMMAND_UI,那么m_bEnableChanged就变成true,接下来就直接返回了。否则,如果bDisableIfNoHndler也为true,那么就向菜单项发一个CN_COMMAND消息,如果你不响应这个消息,说明这个菜单项还没有处理函数,那么,bnHandler就是flase,然后Enable(false)就把你的菜单项变灰了。注意在CFrameWnd::OnInitMenuPopup中调用DoUpdate时的参数是m_bAutoMenuEnable && state.m_nID<0xF000,这说如果你一开始就把m_bAutoMenuEnable设为false的话,实际上就关闭了MFC自动diable没有处理函数的菜单项的功能。    工具栏的更新用的是另外一套方法。首先需要知道当你的的程序变得空闲,没有消息需要处理的时候,MFC会调用CWinApp::OnIdle函数利用这个时间进行一些特殊的工作,其中之一就是更新你的工具栏和状态栏。下面来看相关的代码: BOOL CWinThread::OnIdle(LONG lCount){        if (lCount <= 0){ //依次向main window及其所有子窗口发送WM_IDLEUPDATECMDUI消息,这个消息指示接收窗口进行更新操作               CWnd* pMainWnd = m_pMainWnd;               if (pMainWnd != NULL && pMainWnd->m_hWnd != NULL &&                      pMainWnd->IsWindowVisible())               {                      AfxCallWndProc(pMainWnd, pMainWnd->m_hWnd,                             WM_IDLEUPDATECMDUI, (WPARAM)TRUE, 0);                      pMainWnd->SendMessageToDescendants                         (WM_IDLEUPDATECMDUI,(WPARAM)TRUE, 0, TRUE, TRUE);         } //接下来向本线程创建的所有frame window发送WM_IDLEUPDATECMDUI消息               AFX_MODULE_THREAD_STATE* pState= _AFX_CMDTARGET_GETSTATE()->m_thread;               CFrameWnd* pFrameWnd = pState->m_frameList;               while (pFrameWnd != NULL){                       if (pFrameWnd->IsWindowVisible()||pFrameWnd->m_nShowDelay >= 0){                                    AfxCallWndProc(pFrameWnd, pFrameWnd->m_hWnd,                                           WM_IDLEUPDATECMDUI, (WPARAM)TRUE, 0);                       pFrameWnd->SendMessageToDescendants(WM_IDLEUPDATECMDUI,                                           (WPARAM)TRUE, 0, TRUE, TRUE);                             }               }        } } 你的toolbar或者statusbar总是某个frame window的子窗口(包括子窗口的子窗口…),所以它肯定能收到WM_IDLEUPDATECMDUI消息。CToolBar和CStatusBar都是从CControlBar派生的,下面是CControlBar对这个消息的处理: LRESULT CControlBar::OnIdleUpdateCmdUI(WPARAM wParam, LPARAM) {        if ((GetStyle() & WS_VISIBLE) )        {           //将pTarget指向离this最近的父frame window               CFrameWnd* pTarget = (CFrameWnd*)GetOwner();               if (pTarget == NULL || !pTarget->IsFrameWnd())                      pTarget = GetParentFrame();               //调用虚成员函数OnUpdateCmdUI               if (pTarget != NULL)                      OnUpdateCmdUI(pTarget, (BOOL)wParam);        }        return 0L; } OnUpdateCmdUI是CControlBar类的一个纯虚函数,CToolBar中对这个函数进行了定义: void CToolBar::OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler){        CToolCmdUI state;        state.m_pOther = this;        state.m_nIndexMax = DefWindowProc(TB_BUTTONCOUNT, 0, 0); //工具栏上的按钮数        for (state.m_nIndex=0; state.m_nIndex < state.m_nIndexMax; state.m_nIndex++){          //如果你派生了自己的CToolBar类,那么先让执行你定义的处理函数来进行状态更新          if (CWnd::OnCmdMsg(state.m_nID, CN_UPDATE_COMMAND_UI, &state, NULL))                             continue; //如果toolbar没有更新自己,让pTarget(也就是离它最近的父frame window)来更新它。比如对于MFC自动生成的SDI框架来说,pTarget会指向CMainFrame                      state.DoUpdate(pTarget, bDisableIfNoHndler);               }        }        //如果CToolBar中有用户创建的控件,也一起更新        UpdateDialogControls(pTarget, bDisableIfNoHndler); } CCmdUI::DoUpdate的代码上面已经列出过了。至此,工具栏和状态栏也能顺利也进行更了。 有经验的朋友应该知道,如果你在一个基于对话框的程序里模仿doc/view结构中的方法使用UPDATE_COMMAND_UI来更新用户界面元素的话是不会有任何效果的。其原因是一个模态对话显示出来以后,程序就会进入这个对话框自己的消息循环(看看DoModal的源码就能了解这一点),此时不会再有WM_IDLEUPDATECMDUI被发送到这些界面元素中。下面说说这种情况下的解决办法,你可以自己查看MFC的源码来弄清它的原理:首先加一个头文件afxpriv.h(其中定义了KICKIDLE消息),然后添加一个消息映射来处理WM_KICKIDLE消息:ON_MESSAGE(WM_KICKIDLE,OnKickIdle)。其中OnKickIdle定义如下: LRESULT CTabDialog::OnKickIdle(WPARAM wp, LPARAM lCount){ UpdateDialogControls(this, TRUE); return 0; } 完成这些工作以后,你就可以顺利地使用UPDATE_COMMAND_UI机制了。 ---------------------------   CCmdUI CCmdUI没有基类 它仅在一个CCmdTarget派生类的ON_UPDATE_COMMAND_UI处理程序中使用。 当用户在应用的下拉菜单时,要确定每个菜单项的显示状态——允许存取或禁止存取。菜单命令的目标通过实现一个ON_UPDATE_COMMAND_UI处理来提供这些信息。可以使用ClassWizard来浏览定位应用中的命令用户接口对象,然后为它建立一个消息映射入口,并为每个消息处理函数提供函数原型。 当菜单被下拉时,框架搜索并调用每个ON_UPDATE_COMMAND_UI处理,每个处理调用Enable和Check之类的成员函数,相应地,框架就可以正确地显示每个菜单项了。 菜单项可以用控件条按钮或者其它的命令用户接口对象替换,而在ON_UPDATE_COMMAND_UI处理中的代码不需要改动。 下表列出了各种命令用户接口上的CCmdGUI的成员函数。 用户接口项 Enable SetCheck SetRadio SetText 菜单项 允许或禁止存取该项 选中(ⅹ)或未选中 选中(有黑点) 设置项的文本 工具条按钮 允许或禁止存取该项 选中、未选中或不定 (不可用) 与SetCheck相同 状态条状态提示 文本可见或不可见 设置凸出或正常边框 与SetCheck相同 设置状态条的提示文本 CDialogBar中的普通按钮 允许或禁止存取该项 复选框选中或未选中 与SetCheck相同 设置按钮的文本 CDialogBar中的普通按钮 允许或禁止存取该项 (不可用) (不可用) 设置窗口中的文本 有关使用类CCmdGUI的更详细信息,请参阅联机文档“Visual C++教程”中的“构造用户界面”部分和联机文档“Visual C++程序员指南”中的“如何更新用户界面对象”部分。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值