I would like to have dynamic menu in my application. This dynamic menu should contain popupmenus which will be added and removed on the fly. For the first approach I made dynamic menu created with single menu items.
AppendMenu(menu, MF_STRING, item_id, "TEST");
I have created algorithm which generate item_id and store them in array, so I could remove them by
DeleteMenu(menu, id_to_be_deleted, MF_BYCOMMAND);
I do not see any pitfalls of this implementation and I am happy with it. But then stuck with final implementation. I would like my menus would be popup menus.
new_popup_menu = CreatePopupMenu();
AppendMenu(new_popup_menu, MF_STRING, 1, "TEST1");
AppendMenu(new_popup_menu, MF_STRING, 2, "TEST2");
AppendMenu(new_popup_menu, MF_STRING, 3, "TEST3");
AppendMenu(menu, MF_STRING|MF_POPUP,
(UINT_PTR)new_popup_menu, "dynamic menu");
This code works as expected, but I have no idea how to remove "new_popup_menu" from "menu" since the "UINT_PTR uIDNewItem" parameter of AppendMenu now is used as handle to submenu, not ID and cannot be used with DeleteMenu+MF_BYCOMMAND.
Is there any way to remove this submenu item other then DeleteMenu+MF_BYPOSITION?
Is there a way to get menu item position by handle which is returned by CreatePopupMenu())?
I feel implementation algorithm of tracking which menu is on which position is pain in the ass. Since Windows has API to insert the menu after other specific menu, recreating whole menu tree is a waste of CPU time.
If you want to create a menu item that opens a submenu and has an ID then create it with InsertMenuItem(...) rather than AppendMenu(...). InsertMenuItem(...) lets you fill in a struct that specifies all of the properties you want to be set on the menu item you are creating, including ID and submenu. A lot of Win32 works this way: AppendMenu(...) is like a shorthand version for the more verbose version of the same function. When you run into situations in which you can't do something reasonable with a certain Win32 call, look for a synonymous call that takes a *INFO structure.
Code below:
...
HMENU menu_bar = GetMenu(hWnd);
HMENU new_menu = CreateMenu();
AppendMenu(menu_bar, MF_POPUP, (UINT_PTR)new_menu, "foobar");
AppendMenu(new_menu, MF_ENABLED | MF_STRING, 1002, "item1");
AppendMenu(new_menu, MF_ENABLED | MF_STRING, 1003, "item2");
HMENU dynamic_popup = CreatePopupMenu();
AppendMenu(dynamic_popup, MF_ENABLED | MF_STRING, 1004, "mumble");
AppendMenu(dynamic_popup, MF_ENABLED | MF_STRING, 1005, "quux");
// Below will add an item named "dynamic menu" to the end of new_menu
// that has an ID of 1006.
MENUITEMINFO mii = { 0 };
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_SUBMENU | MIIM_STRING | MIIM_ID;
mii.dwTypeData = (LPSTR)"dynamic menu";
mii.hSubMenu = dynamic_popup;
mii.wID = 1006;
InsertMenuItem(new_menu, 0, FALSE, &mii);
//DeleteMenu(new_menu, 1006, MF_BYCOMMAND);
...
Related
I am using VC6.0. I am trying programming to show contextmenu when I right click on the item of the ListBox. But now the popmenu can show anywhere in the rect of ListBox, since I only can get the rect of the ListBox, and I dont know how to get the rect of the item. I know that there is a macro ListView_GetSubItemRect which seems to get the rect of item of ListView. Is there similar way for ListBox, or is there a way to get the width and the height of item of ListBox, so I can caculate the rect? I didnt find some useful information on msdn and google? Can anyone give me some ideas? Thanks.
My current Code:
void My_OnContextMenu(HWND hwnd, HWND hwndContext, UINT xPos, UINT yPos)
{
HWND hList = GetDlgItem(hwnd,IDC_LIST_RESTYPE);
if (hList == hwndContext)
{
if(-1!=indexLB)
{
RECT rect;
POINT pt;
GetClientRect(hwndContext, &rect);
ScreenToClient(hwndContext, &pt);
if(PtInRect(&rect, pt))
{
HMENU hroot = LoadMenu((HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE), MAKEINTRESOURCE(IDR_MENU_DELTYPE));
if(hroot)
{
HMENU hpop = GetSubMenu(hroot,0);
ClientToScreen(hwndContext, &pt);
TrackPopupMenu(hpop, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwndContext, NULL);
DestroyMenu(hroot);
}
}
}
}
}
Edit
Current:
First, I left click an item to selected、 an item. And Second I right click the selected item to show popmenu. It shows normally. But in the second step if I click the blank area of ListBox, it shows menu either. That is not what I expected.
What I expected is:
The menu only shows when I click an item and the position only over the item. When I right click other area, it wont be showed.
You are looking for the ListBox_GetItemRect macro.
However, I do feel that the user will find it odd to click in one place and see the menu appear somewhere else.
The proper solution to this problem is to popup the context menu at the mouse position. Clicking in one place and popping it up somewhere else would be very bad.
To get the mouse position use GetCursorPos().
http://msdn.microsoft.com/en-us/library/windows/desktop/ms648390%28v=vs.85%29.aspx
To be clear, first use ListBox_GetItemRect to work out which item is clicked on, and ignore it if none. Then use GetCursorPos so the menu appears exactly where the mouse is -- inside the list item -- and not somewhere a few pixels away. The Windows UI standards are that the context menu appears at the cursor position.
I'm not sure Why you wrote your own OnContextMenu - you should use the class wizard to map WM_CONTEXTMENU with the standard handler where the existing function ends up in your code like this:
//Wizard Added this the message map block
ON_WM_CONTEXTMENU()
//Declares the function with the proper parameters
void MyDlg::OnContextMenu(CWnd* pWnd, CPoint point);
//in the body of OnContextMenu use the system supplied parameters and the
//menu will appear next to the mouse position wherever it is clicked in the control
CMenu popupmenu;
popupmenu.LoadMenu(IDR_RMOUSEPOPUP);
int Command = (int)popupmenu.GetSubMenu(0)->TrackPopupMenu(
TPM_LEFTALIGN | TPM_BOTTOMALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY,
point.x, point.y, pWnd);
Years ago, I added an edit control to the toolbar in my application following directions similar to these:
http://www.codeproject.com/Articles/1106/Adding-a-Combo-Box-to-a-Docking-Toolbar
Similar directions can be found in many articles, so I think the procedure is fairly common. Until a few years ago, this worked fine, and the result was as shown in the article. However, I believe the move to XP changed the appearance of buttons in the toolbar, and instead I now see this in my application:
It seems as though the original instructions worked only because controls prior to the change occupied the entire height of the toolbar, so the edit control obstructed the separator behind it.
Ideally, I think the underlying separator should be made invisible. However, this doesn't seem to be handled explicitly in any of the articles I've found, and I'm not quite sure myself how to prevent the separator from being drawn.
Any help would be greatly appreciated. Thanks!
If you follow that article on codeproject exactly, you have probably modified the place holder into a separator from a button. This is why the separator line shown thru when the height of the button image is bigger than the height of the combo box.
If you keep the place holder as an empty button, you will not have such problem. A series of several place holder buttons may be needed in cascaded formation for a really useful length for the combobox.
This technique is demonstrated as follows:
// standard creation of the toolbar in CMainFrame::OnCreate
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
// status bar creation .....
// .....
// the place holders are a series of 5 empty toolbar buttons ie: ID_COMBO_1 to ID_COMBO_5
// get index of first combobox place holder
INT nIndex = m_wndToolBar.GetToolBarCtrl().CommandToIndex(ID_COMBO_1);
// get size of first place holder rectangle
CRect rcRect;
m_wndToolBar.GetToolBarCtrl().GetItemRect(nIndex, &rcRect);
INT nWidth = rcRect.Width();
// calculate width of combobox with sum of all place holder (5 in total)
nWidth = nWidth * 5;
rcRect.top = 5; // top of combo box
rcRect.bottom = rcRect.top + 250; // drop height
rcRect.right = rcRect.left + nWidth;
// create the combobox to sit above the place holders
if(!m_comboBox.Create(CBS_DROPDOWNLIST | CBS_SORT | WS_VISIBLE |
WS_TABSTOP | WS_VSCROLL, rcRect, &m_wndToolBar, ID_COMBO_1))
{
TRACE(_T("Failed to create combo-box\n"));
return FALSE;
}
m_comboBox.AddString("Toolbar Combobox item one");
m_comboBox.AddString("Toolbar Combobox item two");
m_comboBox.AddString("Toolbar Combobox item three");
I've used this technique from CodeGuru. It's old, but, I've been using it for many years and it still works.
I'm using the CMFCPopupMenu to create a right click popup menu. The problem is that the first time the menu is shown only the menu frame with shades is shown but the contents is white. The second time the menu is shown there are no problems. The code looks like this:
CPoint point;
::GetCursorPos (&point);
CMFCPopupMenu* pop = new CMFCPopupMenu();
pop->InsertItem(CMFCToolBarMenuButton(ID_COMMAND_1,NULL,-1,_T("Command 1")));
pop->InsertItem(CMFCToolBarMenuButton(ID_COMMAND_2,NULL,-1,_T("Command 2")));
pop->InsertItem(CMFCToolBarMenuButton(ID_COMMAND_3,NULL,-1,_T("Command 3")));
pop->InsertItem(CMFCToolBarMenuButton(ID_COMMAND_4,NULL,-1,_T("Command 4")));
pop->Create(this,point.x,point.y,NULL,0,true);
The parent class is based on CDialogEx.
Thanks.
I don't understand why my approach doesn't work but I found a way around it by defining the menu in the ressource and do like this:
CMenu menu;
menu.LoadMenu(IDR_SESSION_MENU);
HMENU hMenu = menu.GetSubMenu (0)->Detach ();
CMFCPopupMenu* pMenu = theApp.GetContextMenuManager()->ShowPopupMenu(hMenu, point.x, point.y, this, TRUE);
That Works and the only problem is that it's a bit more complicated to have a menu with dynamic entries depending on state and selection.
CMFCPopupMenu* pPopupMenu = new CMFCPopupMenu;
if (pPopupMenu->Create(pWndOwner, point.x, point.y, NULL, FALSE, TRUE))
{
pPopupMenu->InsertItem(CMFCToolBarMenuButton(57645, NULL, -1, _T("Command 1")), -1);
pPopupMenu->InsertItem(CMFCToolBarMenuButton(57646, NULL, -1, _T("Command 2")), -1);
pPopupMenu->RecalcLayout();
}
Need call RecalcLayout() after insert
What I read is that the menu must have its MenuInfo.dwStyle flag set to MNS_NOTIFYBYPOS, what I did is:
MENUINFO MenuInfo;
memset(&MenuInfo,0, sizeof(MenuInfo));
MenuInfo.cbSize = sizeof(MenuInfo);
HMENU hPopupMenu = CreatePopupMenu();
GetMenuInfo(hPopupMenu, &MenuInfo);
MenuInfo.dwStyle |= MNS_NOTIFYBYPOS;
SetMenuInfo(hPopupMenu, &MenuInfo);
And next proceed with adding items:
InsertMenu(hPopupMenu, pos, MF_BYPOSITION, id , "do command");
Next track it:
TrackPopupMenu(hPopupMenu, TPM_CENTERALIGN | TPM_RETURNCMD, cursorPos.x, cursorPos.y, 0, hwnd, NULL);
But it has no effect, it compiles without error but the clicking event is till send as WM_COMMAND
You need to set the fMask member of the MENUINFO structure to tell the system which fields you want to set/get.
E.g.
MenuInfo.fMask = MIM_STYLE;
GetMenuInfo(hPopupMenu, &MenuInfo);
MenuInfo.dwStyle |= MNS_NOTIFYBYPOS;
SetMenuInfo(hPopupMenu, &MenuInfo);
Also note that the docs say:
MNS_NOTIFYBYPOS is a menu header style and has no effect when applied
to individual sub menus.
So it is possible that it won't work for you anyway with a popup menu.
I ported my MFC application to Feature pack.When i try to insert a new sub menu/popup menu to CMFCMenuBar, the menu items in "Window" menu gets duplicated. Kindly help me. I used the below code to insert submenu.
CMenu* pMenu;
HMENU hMenu = m_wndMenuBar.GetHMenu();
ASSERT(::IsMenu(hMenu));
pMenu = CMenu::FromHandle(hMenu);
pMenu = pMenu->GetSubMenu(2);
pMenu->InsertMenu(2, MF_BYPOSITION ,
(UINT)ID_SORTING_SORTBYACCESS, _T("My Menu"));
m_wndMenuBar.CreateFromMenu(hMenu, false, true);