Creating Dynamic Drop Menus on Toolbar Buttons
There's an old, trite saying: "Necessity is the mother of invention." Since I'm an old, trite programmer, this saying worked well for me with code I needed to write for one of our wireless applications. The application uses "snap-in" DLL technology for adding data modes to the program. All that's needed by the end user is to put a DLL in the same folder as the program, and the snap-in DLL adds this mode to its internal list. Depending on which DLLs are installed, I needed a drop-down menu off the toolbar to let the user choose the mode he or she wants, based on the DLLs that are installed.
Many articles have been written on how to write code to create and process drop menus from a toolbar button, but I couldn't find one on creating and processing dynamic menus. I knew that the functionality existed, though, so I went to the Windows API for help. This article shows you how to do both dynamic and static drop menus for two toolbar buttons.
NOTE
The important thing to remember about these drop menus is that they're menus with the pop-up style bit set. For static menus, you'll need to create them in the resource editor. With the dynamic menus, a function call will create a pop-up menu for you on the fly.
This code in C++ was used in an RF modem application where the modes could change on the fly if a DLL was added to the folder where the application resided. Therefore, we can rebuild the dynamic menu every time it's clicked, reflecting the latest additions in the snap-in DLL.
Let's Get Started
First, you need to set the toolbar for extended styles and allow the TBSYLE_EX_DRAWDARROWS to be set, so you can have an arrow on the button that will handle the drop menu:
DWORD dwExStyle = TBSTYLE_EX_DRAWDDARROWS; m_toolbar.GetToolBarCtrl().SendMessage(TB_SETEXTENDEDSTYLE, 0, LPARAM)dwExStyle);
Next, set the button where you want to have the menu to the TBSTYLE_DROPDOWN style:
DWORD dwStyle = m_toolbar.GetButtonStyle(m_toolbar.CommandToIndex(ID_MYBUTTONWITHDYNAMICMENU); dwStyle |= TBSTYLE_DROPDOWN; m_toolbar.SetButtonStyle(m_hftoolbar.CommandToIndex(ID_MYBUTTONWITHDYNAMICMENU), dwStyle); m_toolbar.GetButtonStyle(m_toolbar.CommandToIndex(ID_MYBUTONWITHSTATICMENU); m_toolbar.SetButtonStyle(m_hftoolbar.CommandToIndex(ID_MYBUTONWITHSTATICMENU), dwStyle);
If you compile, link, and run the code now, you'll see an arrow on the button you selected to be the "chosen one."
The next step is to process the drop-down message; you can do this with the ON_NOTIFY macro and the resource ID of the toolbar:
ON_NOTIFY(TBN_DROPDOWN, IDR_HF_FRAME, OnDropDownMenu)
OnDropDownMenu is the function that will create the menu on the fly and assign dynamic or static menus to the buttons you chose. In this example, based on the toolbar button that's pressed, it either creates a dynamic drop-down menu or one based out of a static resource:
void CWindowFrame::OnDropDownMenu(UINT uint, NMHDR * pnmtb_1, LRESULT *plr) { CWnd *pWnd; UINT nID; NMTOOLBAR* pnmtb = (NMTOOLBAR *) pnmtb_1; // which button was pressed? switch (pnmtb->iItem) { case ID_MYBUTTONWITHDYNAMICMENU: nID = ID_MYBUTTONWITHDYNAMICMENU; break; case ID_MYBUTTONWITHSTATICMENU: nID = ID_MYBUTTONWITHSTATICMENU; break; default: return; } // create the menu CMenu * menu; menu = new CMenu; switch (nID) { case ID_MYBUTTONWITHDYNAMICMENU: menu->CreatePopupMenu(); // create the popup menu dynamically for (int i=0;i<64;i++) { if (m_hhmodearray[i].mode !=0) { menu->AppendMenu(MF_STRING,WM_USER+32767+i,m_hhmodearray[i].modename); } } break; case ID_MYBUTTONWITHSTATICMENU: menu->LoadMenu(IDR_STAIC_MENU); // create the popup menu with a static resource. menu=menu->GetSubMenu(0); break; } ASSERT(pPopup); CRect rc; ::SendMessage(m_toolbar.m_hWnd,TB_GETRECT, pnmtb->iItem, (LPARAM)&rc); m_toolbar.ClientToScreen(&rc); menu->TrackPopupMenu( TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL, rc.left, rc.bottom, this, &rc); }
In the code above, the first thing we need to do is typecast the NMHDR * parameter to an NMTOOLBAR * datatype. This gives us access to the toolbar.
Now, based on which button is pressed, we create the pop-up menu. I move it into a variable for processing, just in case the pnmtb->iItem gets changed. If the button is for the static menu, we get it from the menu resource that we created in the resource editor. If the button pressed is for the dynamic menu, that's where the interesting code is.
In the above case, in the init code, I loaded string names and ID numbers from a DLL into an array of 64 structures. Because the DLL changes those strings and ID numbers on the fly, using dynamic menus allows us to change the menus on the fly, when the DLL changes. Since I have an array of string names and modem IDs, I can use it to create the dynamic menus and associated IDs.
Now we need to add the menus and their dynamic resource IDs to the drop menu. We do this with the function call AppendMenu:
menu->AppendMenu(MF_STRING,WM_USER+32767+i,m_hhmodearray[i].modename);
I use a FOR loop around the code so I can iterate each string name and set up the dynamic IDs.
CAUTION
Make sure that the number you choose to create the dynamic IDs isn't equivalent to any you've already defined with WM_USER. I chose 32,767 as an arbitrary number.
The TrackPopUpMenu function, since it's assigned to that button on the toolbar, displays a shortcut menu at the specified location and tracks the selection of items on the menu. This way, your menu pops up on the toolbar and is able to process the dynamic IDs associated with it.
Now I can use the ON_COMMAND_RANGE macro to see whether one of those dynamic IDs was hit when the menu was clicked:
ON_COMMAND_RANGE(WM_USER+32767,WM_USER+32767+64,DoFunction)
And then use DoFunction to process whichever command it was:
void CHfWindowFrame::DoFunction(UINT mode) { int i = mode - (WM_USER+32767); HamHubMode(m_hhmodearray[i].modename,m_hhmodearray[i].mode); }
I subtract WM_USER+32767 from the menu ID and get the mode number for the DLL; then I make the call to the DLL with the correct mode and mode name in the array. You can modify this code as needed for your dynamic and static drop menus.