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






4.50/5 (4投票s)
通过外部 XML 文件动态激活应用程序菜单和工具栏。
引言
这篇文章讨论了一个已经被讨论过数千次的主题。实际上,对于在 MFC 应用程序中集成动态菜单,它并没有带来什么新内容,因为我重新使用了许多现有的代码片段。但是,我决定发布我的贡献,以分享我在开发以下两个主题时获得的经验:
- 从外部 XML 文件配置菜单和工具栏。
- 通过连接多个外部 BMP 文件来创建位图。
背景
已经发布了许多关于这个主题的示例,我利用了这些之前的开发成果。对我来说,主要帮助我入门的两篇文章是:CreatingDynamicToolbars 和 DynaMenuToolbar。也感谢其他人。
使用代码
我没有将我的代码放入库中,但可以通过将名为 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);
}
}
}
改进
- 此代码最初是为与 Stingray [Rogue Wave] 库一起使用而创建的。使用此库,您可以在多个工具栏之间共享单个位图,而纯 MFC 则不然。为了处理多个动态工具栏,我们应该将
ToolbarBitmap
移动到 MenuToolbar 级别。 - 从 XML 文件读取的菜单的创建可以更通用。
- 将弹出菜单插入主菜单栏的菜单搜索可以更通用。