Advanced CMenuExtender Techniques for Dynamic Menus Introduction
The standard CMenu class in MFC (Microsoft Foundation Classes) provides basic control over desktop application menus. However, modern user interfaces require menus that adapt dynamically to user roles, runtime configurations, and real-time application states. Building a custom CMenuExtender class allows developers to inject advanced behavior into native Win32 menus without rewriting core boilerplate code. This article covers advanced architectural patterns for extending CMenu to handle dynamic content, runtime icon injection, and conditional command routing. Core Architecture of CMenuExtender
To build an effective menu extender, you must subclass or wrap the native CMenu handle (HMENU). The primary design goal is intercepting the menu rendering lifecycle and command routing mechanism.
class CMenuExtender { public: CMenuExtender(CMenupTargetMenu); virtual ~CMenuExtender(); // Advanced dynamic manipulation BOOL AppendDynamicItem(UINT nID, LPCTSTR lpszText, HICON hIcon = NULL); BOOL InsertDynamicItem(UINT nPosition, UINT nID, LPCTSTR lpszText, HICON hIcon = NULL); // Lifecycle hooks virtual void RefreshMenuStructure(); protected: CMenu* m_pMenu; CMap Use code with caution.
This structural foundation hooks directly into an existing MFC menu instance, allowing you to manipulate items by index or command ID at runtime. Runtime Icon Injection via Owner-Draw
Standard Win32 menus support bitmaps out of the box, but handling high-DPI modern HICON elements with alpha channels requires an owner-draw approach. CMenuExtender simplifies this by handling the WM_MEASUREITEM and WM_DRAWITEM messages. When appending an item, mark it with the MF_OWNERDRAW flag:
BOOL CMenuExtender::AppendDynamicItem(UINT nID, LPCTSTR lpszText, HICON hIcon) { ASSERT(m_pMenu != NULL); // Store icon reference for the draw lifecycle if (hIcon) { m_mapIcons.SetAt(nID, hIcon); } // Append as owner-draw item return m_pMenu->AppendMenu(MF_STRING | MF_OWNERDRAW, nID, lpszText); } Use code with caution. Handling Draw Lifecycle
Your parent window (typically CMainFrame) must forward the menu measurements and drawing events to the extender:
void CMenuExtender::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) { if (lpMeasureItemStruct->CtlType == ODT_MENU) { // Provide standard dimensions + spacing for icons lpMeasureItemStruct->itemWidth = 150; lpMeasureItemStruct->itemHeight = 24; } } void CMenuExtender::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) { if (lpDrawItemStruct->CtlType != ODT_MENU) return; CDC dc; dc.Attach(lpDrawItemStruct->hDC); CRect rect(lpDrawItemStruct->rcItem); UINT itemID = lpDrawItemStruct->itemID; // Background rendering based on state if (lpDrawItemStruct->itemState & ODS_SELECTED) { dc.FillSolidRect(rect, GetSysColor(COLOR_HIGHLIGHT)); dc.SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT)); } else { dc.FillSolidRect(rect, GetSysColor(COLOR_MENU)); dc.SetTextColor(GetSysColor(COLOR_MENUTEXT)); } // Draw Icon if registered HICON hIcon = NULL; if (m_mapIcons.Lookup(itemID, hIcon) && hIcon) { DrawIconEx(dc.GetSafeHdc(), rect.left + 4, rect.top + 4, hIcon, 16, 16, 0, NULL, DI_NORMAL); } // Draw text manually because of MF_OWNERDRAW CString strText; m_pMenu->GetMenuString(itemID, strText, MF_BYCOMMAND); rect.left += 28; // Shift text right to accommodate the icon dc.DrawText(strText, rect, DT_SINGLELINE | DT_VCENTER); dc.Detach(); } Use code with caution. Dynamic Context-Aware Filtering
A frequent requirement in enterprise applications is filtering menu options based on current database states or user authorization profiles. Hardcoding these rules into resource files is impossible.
The pattern below uses a registration callback mechanism within CMenuExtender to evaluate visibility before rendering.
typedef std::function Use code with caution. Practical Implementation
You can execute this right before the menu is displayed to the user, typically inside the OnInitMenuPopup handler of your frame window:
void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) { CFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu); if (!bSysMenu && pPopupMenu) { CAdvancedMenuExtender extender(pPopupMenu); // Bind dynamic logic (e.g., current user permissions) extender.RegisterVisibilityRule(this -> bool { if (nID == ID_ADMIN_TOOLS) { return this->GetCurrentUser().IsAdmin(); } return true; }); extender.EvaluateVisibility(); } } Use code with caution. Lazy-Loading Hierarchical Submenus
Populating large, deep menu trees (such as a folder directory structure or a registry tree) during application boot hurts performance. An advanced CMenuExtender uses lazy loading to query and build submenus only when a user expands a specific parent node.
Add a dummy item placeholder under the expandable dynamic parent menu. Intercept WM_INITMENUPOPUP.
Detect if the opening submenu contains the dummy placeholder. Clear the placeholder and fetch real data on demand.
void CMenuExtender::PrepareLazySubmenu(CMenu* pSubMenu, UINT nPlaceholderID) { int count = pSubMenu->GetMenuItemCount(); if (count == 1 && pSubMenu->GetMenuItemID(0) == nPlaceholderID) { // Clear placeholder pSubMenu->DeleteMenu(0, MF_BYPOSITION); // Fetch runtime details (Database, File System, API) std::vector Use code with caution. Conclusion
By implementing a robust CMenuExtender, you decouple dynamic UI manipulation from the primary window event router. Embracing owner-draw routines enables seamless icon styling, while pre-render callbacks and lazy-loading techniques keep your UI layout crisp, responsive, and tailored strictly to runtime permissions and context.
If you would like to explore specific implementations of these concepts, please let me know:
Which MFC UI framework you are targeting (Classic Win32, ribbon-based, or BCGControlBar)?
Whether you need to pass custom data pointers to your menu items?
Leave a Reply