Many applications allow dynamic customization for visual objects or data views. For instance, well known Internet Explorer application provides toolbars customization using a popup menu that appears when the user execute right click mouse action in toolbar zone area.
Other sample where this kind of menu is very useful is when it’s used in order to customize database data representation in Windows controls like List control or grid control. These kind of applications allow data filtering and show/hide columns using this kind of menu. The user just right click on control header and gets what he need.
Starting from this idea, I implemented a class CDynamicPopupMenu. This class allows an easy building of this kind of menus. I used if in a demo dialog base application over a list control.
Internally, this class uses a STL container (std::map) with a data structure used in order to embed items menu properties. When the menu is built, menu’s behavior is implemented using these properties.
Add new menu item
The new item add menu method has next definition:
void AddItemData(const int item_id, const int parent_id, bool is_visible, bool check_flag, bool has_child, const std::wstring item_name, bool enable_flag);
where:
- item_id – represents internal item ID; the ID is used for menu customization, too;
- parent_id – parent item ID used when we define a new items sub-group (a drop-down menu); the attribute value is 0 if menu item is a part of initial menu;
- is_visible – this flag is used a item is checked / unchecked. In my demo application this flag is set true for all list control’s columns that we want to display. For “Select All” and “Check All” items this flag is false because we want to create new subgroup that contains new columns, but we don’t have “Select All” or “Check All” columns.
- check_flag – this flag allow check/uncheck menu property;
- has_child – if is true allows a subgroup definition (a new drop-down menu);
- item_name – unicode menu item name;
- enable_flag – defines if the item is enable or disable.
Add separator item
Add separator item method definition looks like this:
void AddItemSeparator(const int item_id, const int parent_id);
where:
- item_id – menu item ID;
- parent_id – parent item ID from the subgroup has started; the attribute value is 0 if menu item is a part of initial menu.
Menu add items sample
In my demo application, in CtestPopupMenuDlg::SetDefaultMapValues(void) method, among other things, you can find next calls:
m_pCustPPMenu->AddItemData(MI_MAINITEM_1, 0, true, true, false, _T("Item 1"), true); m_pCustPPMenu->AddItemSeparator(MI_MAIN_SEP1,0); m_pCustPPMenu->AddItemData(MI_MAINITEM_4_GROUP_1, 0, true, true, true, _T("Group 1"), true); m_pCustPPMenu->AddItemData(MI_GROUP_1_SUBITEM_1, MI_MAINITEM_4_GROUP_1, false, false, false, _T("G1-Select All"), true); m_pCustPPMenu->AddItemData(MI_GROUP_1_SUBITEM_2, MI_MAINITEM_4_GROUP_1, true, false, false, _T("G1-Item 12"), true);
Get menu internal data
In order to access the internal data container (std::map) that stores all dynamic menu items you just can use next method:
DynamicMenuData* GetMenuData();
followed by:
DynamicMenuData* pItemsMap = m_pCustPPMenu->GetMenuData();
Create and display menu
Menu creation must be done just after we add all menu items. The menu is displayed only after TrackPopupCustomMenu() call. The definition of this method looks like this:
DWORD TrackPopupCustomMenu(POINT point, HWND hWnd);
where:
- point – mouse coordinates where the menu start building;
- hWnd – parrent window handle where the menu is created.
Function’s return value is the menu IDs that was selected. If no item was selected the function returns 0.
In my demo application, menus creation is called on list control right-click method (NM_RCLICK).
void CtestPopupMenuDlg::OnNMRclickList1(NMHDR *pNMHDR, LRESULT *pResult) { POINT point; ::GetCursorPos(&point); int nSelItem = m_pCustPPMenu->TrackPopupCustomMenu(point, m_ctrlList.m_hWnd); if (0 < nSelItem) { pNMHDR->hwndFrom = m_ctrlList.m_hWnd; pNMHDR->idFrom = nSelItem; pNMHDR->code = WM_NOTIFY; OnNotify( 0, (LPARAM)pNMHDR, pResult); } *pResult = 0; }
As you can see, I’m calling TrackPopupCostumMenu(), using mouse point property when the user right-click over list control.
I am saving list control handler, selected item ID and WM_NOTIFY value into a pointer to message notification structure NMHDR. Then I’m passing this pointer to OnNotify() method.
Using WM_NOTIFY message and OnNotify() method, I inform parent control window that a new event was generated.
BOOL CtestPopupMenuDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) { NMHDR *p = (NMHDR*) lParam; _ASSERT(p); _ASSERT(m_pCustPPMenu); bool bFlag = false; switch (p->idFrom) { case MI_MAINITEM_1: m_pCustPPMenu->GetItemCheckedFlag(MI_MAINITEM_1, bFlag); m_pCustPPMenu->SetCheckedItemFlag(MI_MAINITEM_1, !bFlag); FillData(); break; case MI_MAINITEM_2: m_pCustPPMenu->GetItemCheckedFlag(MI_MAINITEM_2, bFlag); m_pCustPPMenu->SetCheckedItemFlag(MI_MAINITEM_2, !bFlag); FillData(); break; // ======================= // Silviu: this method store other menu items handlers, too // ======================= default: break; } return CDialog::OnNotify(wParam, lParam, pResult); }
I am calling GetItemCheckedFlag() if order to detect selected item check status (check / uncheck). Then, if item state means check I apply negation over this bool flag and I’m calling SetCheckedItemFlag() method. Finnaly this method produce changes in my control list, depending on my menu command (FillData() method).
Menu interaction with parent window (list control)
In my demo application, the interaction between dynamic menu and list control to be treated by FillData() method.
In order to use CDynamicPopupMenu’s internal container data is need to initialize a DynamicMenuData pointer with GetDynamicMenuData()’s returned value.
void CtestPopupMenuDlg::FillData() { _ASSERT(m_pCustPPMenu); DynamicMenuData *pItemsMap = m_pCustPPMenu->GetDynamicMenuData(); int nCol = 0; if ((NULL != pItemsMap) && (!pItemsMap->empty())) { // reset columns int nColumnCount = m_ctrlList.GetHeaderCtrl()->GetItemCount(); for (int i=0;i < nColumnCount;i++) m_ctrlList.DeleteColumn(0); for (iterDynMenu itm = pItemsMap->begin(); itm != pItemsMap->end(); ++itm) { if (m_pCustPPMenu->GetIsVisible(itm) && m_pCustPPMenu->GetIsChecked(itm)) { m_ctrlList.InsertColumn(nCol++, itm->second.sItemName.c_str(), LVCFMT_LEFT, 70); } } } }
Using that pointer to internal menu data, I iterate over internal container, and for those items that are visible and selected set on true I insert columns in my list control.
Similarly, when using such menus, the application can apply filters on real data.
CDynamicPopupMenu class contains other useful methods. This kind of menu can be used in different situations in order to change application’s behavior.
Download demo application: testPopupMenu (Visual C++ 2005 project)