65.9K
CodeProject 正在变化。 阅读更多。
Home

MFC 菜单和工具栏的动态配置

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (4投票s)

2009年3月25日

CPOL

2分钟阅读

viewsIcon

43104

downloadIcon

2488

通过外部 XML 文件动态激活应用程序菜单和工具栏。

引言

这篇文章讨论了一个已经被讨论过数千次的主题。实际上,对于在 MFC 应用程序中集成动态菜单,它并没有带来什么新内容,因为我重新使用了许多现有的代码片段。但是,我决定发布我的贡献,以分享我在开发以下两个主题时获得的经验:

  1. 从外部 XML 文件配置菜单和工具栏。
  2. 通过连接多个外部 BMP 文件来创建位图。

背景

已经发布了许多关于这个主题的示例,我利用了这些之前的开发成果。对我来说,主要帮助我入门的两篇文章是:CreatingDynamicToolbarsDynaMenuToolbar。也感谢其他人。

使用代码

我没有将我的代码放入库中,但可以通过将名为 commands 的子目录复制到任何 MDI(或 SDI)项目中轻松重用。

要进行正确的连接,必须按照如下描述修改 CMainFrame。首先,您直接在构造函数中创建一个 MenuCommandSet

//in MainFrame.h you add a private member
private:
    MenuCommandSet* m_pMenuCommandSet;


//that you can create directly in the constructor
CMainFrame::CMainFrame()
{
    m_pMenuCommandSet=    new MenuCommandSet();
}

//... and destroy when finished
CMainFrame::~CMainFrame()
{
    delete m_pMenuCommandSet;
}

MenuCommandSet的初始化是在框架创建时进行的

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
    
    if(NULL==m_pMenuCommandSet)
        return -1;

    //check creation of the command set as it may failed 
    //in case the xml file is lost or corrupted
    if(false==m_pMenuCommandSet->Create"the_cfg_file_to_use.xml",
              COMMAND_SET_BASE_ID))
        return -1;

    //get the handle on the toolbar bitmap dynamically created
    HBITMAP hBitmap= (HBITMAP)m_pMenuCommandSet->GetToolbarBitmap().GetHBitmap();
    //check if creation has been successfull
    if(NULL==hBitmap)
        return -1;

    ...
}

现在初始化已经完成,我们使用它来创建和初始化我们的动态工具栏

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    ...

    //then create ask for the first dynamic toolbar
    MenuToolbar* pToolbar= m_pMenuCommandSet->GetMenuToolbar(MenuIdToolbar1);
    if(pToolbar)
    {
        //create the toolbar control 
        if (!m_wndDynaToolBar.CreateEx(this, 
            TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP |
            CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) )
        {
            TRACE("Failed to create m_wndToolBar2\n");
            return -1;      // fail to create
        }
        //attach the dynamic bitmap to the toolbar
        m_wndDynaToolBar.SetBitmap(hBitmap);    
        //intialize it with the buttons found in the XML configuration file
        m_wndDynaToolBar.SetButtons(pToolbar->GetIconIDs(),
                       pToolbar->GetIconCount());
    }

    ...

    m_wndDynaToolBar.EnableDocking(CBRS_ALIGN_ANY);
    EnableDocking(CBRS_ALIGN_ANY);
    DockControlBar(&m_wndDynaToolBar);

    ...
}

然后,我们添加挂钩以将消息字符串显示在状态栏中

void CMainFrame::GetMessageString(UINT nID, CString& rMessage) const
{
    if(m_pMenuCommandSet && m_pMenuCommandSet->IsInRange(nID)){
        MenuItem oItem;
        if(true==m_pMenuCommandSet->GetMenuItem(nID, oItem)){
            rMessage= oItem.GetStatus();
        }
        return;
    }
    CMDIFrameWnd::GetMessageString(nID, rMessage);
}

为工具栏的每个按钮显示提示

BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
    ...
    ON_NOTIFY_EX(TTN_NEEDTEXT, 0, OnShowTooltips)
    ...
END_MESSAGE_MAP()


BOOL CMainFrame::OnShowTooltips(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
{
    id;            //parameter not used
    pResult;    //parameter not used
    TOOLTIPTEXT* pTTT = (TOOLTIPTEXT*)pNMHDR;
    UINT nID =            (UINT)pNMHDR->idFrom;

    CString sToolTip;
    CString sFullText;
    if(m_pMenuCommandSet && m_pMenuCommandSet->IsInRange(nID))
    {
        MenuItem oItem;
        if(true==m_pMenuCommandSet->GetMenuItem(nID, oItem)){
            sToolTip= oItem.GetTooltip();
            strcpy_s(pTTT->szText, sizeof(pTTT->szText), (LPCTSTR)sToolTip);
        }
        return TRUE;
    }

    if(nID){
        sFullText.LoadString(nID);
        ::AfxExtractSubString(sToolTip, sFullText, 1, '\n');
        strcpy_s(pTTT->szText, _countof(pTTT->szText), sToolTip);
        return TRUE;
    }

    return FALSE;

当然,还要处理对我们的动态菜单的点击

BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, 
                               void* pExtra, 
                               AFX_CMDHANDLERINFO* pHandlerInfo)
{
    if(NULL==pHandlerInfo)
    {
        // Filter the commands sent to Window menu
        if(m_pMenuCommandSet && m_pMenuCommandSet->IsInRange(nID))
        {
            if (nCode == CN_COMMAND){
                // Handle WM_COMMAND message
                m_pMenuCommandSet->DoCommandMenu(nID);
            }else if (nCode == CN_UPDATE_COMMAND_UI){
                // Update UI element state
                m_pMenuCommandSet->DoUpdateMenu(nID, (CCmdUI*)pExtra);
            }
            return TRUE;
        }
    }

    return     CMDIFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}

为了避免与其他标准消息发生冲突,不要忘记在 resource.h 文件中保留一组 ID。第一个 ID (ID_CMDSET_RESERVED_1) 将提供给 MenuCommandSet,以告知它应该处理哪些命令。

//in resource.h

#define ID_CMDSET_RESERVED_1            32770
#define ID_CMDSET_RESERVED_2            32771
#define ID_CMDSET_RESERVED_3            32772
#define ID_CMDSET_RESERVED_4            32773
#define ID_CMDSET_RESERVED_5            32774
...

//in CMainFrame::OnCreate()
    if(false==m_pMenuCommandSet->Create("the_cfg_file_to_use.xml", 
                                        COMMAND_SET_BASE_ID))
        return -1;

如果要将一些菜单插入主菜单栏中,您需要定义 On-Update-Command 处理程序,在那里您可以插入您定义的弹出菜单。

BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
   ...
   ON_UPDATE_COMMAND_UI(ID_DYNA_POPUP_MENU1, OnUpdateDynaMenu1)
   ...
END_MESSAGE_MAP()


void CMainFrame::OnUpdateDynaMenu1(CCmdUI* pCmdUI)
{
    pCmdUI; //parameter not used
    CMenu* pMenu= _GetDynaPopupMenu1();
    if(pMenu && m_pMenuCommandSet)
    {
        MenuPopup* pMenuPopup= 
          m_pMenuCommandSet->GetMenuPopup(MenuIdPopup1);
        if(pMenuPopup){
            pMenuPopup->AddToMenu(pMenu);
        }
    }
}

改进

  1. 此代码最初是为与 Stingray [Rogue Wave] 库一起使用而创建的。使用此库,您可以在多个工具栏之间共享单个位图,而纯 MFC 则不然。为了处理多个动态工具栏,我们应该将 ToolbarBitmap 移动到 MenuToolbar 级别。
  2. 从 XML 文件读取的菜单的创建可以更通用。
  3. 将弹出菜单插入主菜单栏的菜单搜索可以更通用。
© . All rights reserved.