Introduction
This article deals with a subject that has already been treated several thousand of times. Actually, it does not bring anything really new regarding the integration of dynamic menus within an MFC application since I have re-used a lot of existing pieces of code. But, I decided to publish my modest contribution in order to share my experience gained while developing the following two topics:
- The configuration of menus and toolbars from an external XML file.
- The creation of a bitmap by the concatenation of multiple external BMP files.
Background
A lot of samples treating this subject have already been published, and I took advantage of these previous developments. Two articles that mainly helped me to start were: CreatingDynamicToolbars and DynaMenuToolbar. Thanks also to all the others.
Using the code
I did not put my code into a library, but it can be easily re-used by copying the sub directory called commands into any MDI (or SDI) project.
To do a proper connection, CMainFrame
must be modified as described shown below. First, you create a MenuCommandSet
directly in the constructor:
private:
MenuCommandSet* m_pMenuCommandSet;
CMainFrame::CMainFrame()
{
m_pMenuCommandSet= new MenuCommandSet();
}
CMainFrame::~CMainFrame()
{
delete m_pMenuCommandSet;
}
The initialization of the MenuCommandSet
is made at the frame creation:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if(NULL==m_pMenuCommandSet)
return -1;
if(false==m_pMenuCommandSet->Create"the_cfg_file_to_use.xml",
COMMAND_SET_BASE_ID))
return -1;
HBITMAP hBitmap= (HBITMAP)m_pMenuCommandSet->GetToolbarBitmap().GetHBitmap();
if(NULL==hBitmap)
return -1;
...
}
Now that the initialization has been made, we use it to create and initialize our dynamic toolbar:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
...
MenuToolbar* pToolbar= m_pMenuCommandSet->GetMenuToolbar(MenuIdToolbar1);
if(pToolbar)
{
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; }
m_wndDynaToolBar.SetBitmap(hBitmap);
m_wndDynaToolBar.SetButtons(pToolbar->GetIconIDs(),
pToolbar->GetIconCount());
}
...
m_wndDynaToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndDynaToolBar);
...
}
Then, we add the hooks to display the message string into the status bar:
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);
}
To show a tooltip for each button of the toolbar:
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; pResult; 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;
And, of course, to handle the clicks on our dynamic menus:
BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode,
void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
if(NULL==pHandlerInfo)
{
if(m_pMenuCommandSet && m_pMenuCommandSet->IsInRange(nID))
{
if (nCode == CN_COMMAND){
m_pMenuCommandSet->DoCommandMenu(nID);
}else if (nCode == CN_UPDATE_COMMAND_UI){
m_pMenuCommandSet->DoUpdateMenu(nID, (CCmdUI*)pExtra);
}
return TRUE;
}
}
return CMDIFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
In order to avoid collisions with other standard messages, do not forget to reserve a bunch of IDs directly within the resource.h file. The first one (ID_CMDSET_RESERVED_1
) is given to the MenuCommandSet
to let it know which commands it should handle.
#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
...
if(false==m_pMenuCommandSet->Create("the_cfg_file_to_use.xml",
COMMAND_SET_BASE_ID))
return -1;
If you want to insert some menus inside the main menu bar, you define the On-Update-Command handler, where you can insert the popup menu you have defined.
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
...
ON_UPDATE_COMMAND_UI(ID_DYNA_POPUP_MENU1, OnUpdateDynaMenu1)
...
END_MESSAGE_MAP()
void CMainFrame::OnUpdateDynaMenu1(CCmdUI* pCmdUI)
{
pCmdUI; CMenu* pMenu= _GetDynaPopupMenu1();
if(pMenu && m_pMenuCommandSet)
{
MenuPopup* pMenuPopup=
m_pMenuCommandSet->GetMenuPopup(MenuIdPopup1);
if(pMenuPopup){
pMenuPopup->AddToMenu(pMenu);
}
}
}
Improvements
- This code was initially created to be used with the Stingray [Rogue Wave] library. Using this library, you can share a single bitmap between several toolbars, which is not the case in pure MFC. To handle several dynamic toolbars, we should move
ToolbarBitmap
to the MenuToolbar level. - The creation of the menus read from the XML file could be more generic.
- The search for menus for the insertion of the pop-ups into the main menu bar could be more generic.