Is there a way to create dynamic menu items in the menu bar of an MFC applications created with visual studio 2008 style with menus not ribbon, I have plugins that add their menus dynamically how can I add them to that menu??? I see
//this is the menubar that i want to update
CMFCMenuBar m_wndMenuBar;
At first you have to create a new submenu. It can be filled by hand or loaded from resources.
Then create a new menu button and attach submenu to it. After that update the menu bar. That's it.
CMenu menu;
if(menu.LoadMenu(IDR_MY_MENU))
{
m_wndMenuBar.InsertButton(CMFCToolBarMenuButton(0, menu, -1, _T("&MyMenu")), -1);
m_wndMenuBar.AdjustLayout();
m_wndMenuBar.AdjustSizeImmediate();
}
P.S. menu may be local because CMFCToolBarMenuButton constructor copy content from it.
You probably need to override:
CMDIFrameWndEx::OnShowPopupMenu(CMFCPopupMenu* pMenuPopup)
and put a placeholder menu item where you need a dynamic menu, then replace the placeholder menu item with your dynamic menu.
For an example look into the source code shipping with MFC:
afxframeimpl.cpp
BOOL CFrameImpl::OnShowPopupMenu(CMFCPopupMenu* pMenuPopup, CFrameWnd* /*pWndFrame*/)
The ideal way is probably to completely build a CMenu object, and then assign it to the menu bar: This makes the code almost compatible with the previous versions of VisualStudio.
Declare a pointer to an instance of CMenu in CMainFrame definition:
CMenu *m_pMainMenu
Set it to NULL in CMainFrame constructor:
m_pMainMenu = NULL;
In CMainFrame::OnCreate(), create an instance of CMenu assuming it was NULL before:
SAFE_DELETE(m_pMainMenu);
m_pMainMenu = new CMenu;
Modify it at will with:
pMainMenu->LoadMenu(),
pMainMenu->RemoveMenu(),
pMainMenu->AppendMenu(),
pMainMenu->InsertMenu(),
...
Finally, associate the menu toolbar with this new menu by using CreateFromMenu:
m_wndMenuBar.CreateFromMenu(m_pMainMenu->m_hMenu);
Don't forget to release m_pMainMenu in CMainFrame destructor to avoid leaks:
SAFE_DELETE(m_pMainMenu)
Note: This is a very handy and safe macro:
#define SAFE_DELETE(a) { if (a) { delete(a); a=NULL; } }
You can use UPDATE_COMMAND_UI in property view window.
Let's say, you put a 'Text Capture' menu with 'ID_TEXTCAPTURE', and then you want to change the menu with 'Stop Capture'.
-in message map
BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWndEx)
ON_WM_CREATE()
ON_UPDATE_COMMAND_UI(ID_TEXTCAPTURE, &CChildFrame::OnUpdateTextcapture)
END_MESSAGE_MAP()
-event handler
void CChildFrame::OnUpdateTextcapture(CCmdUI *pCmdUI)
{
if(pCmdUI->m_pMenu)
{
int pos;
CMenu* mmenu = pCmdUI->m_pMenu;
pos = FindMenuItem(mmenu, _T("Text Capture(&C)"));
if (pos > -1 && IsCapture == TRUE)
{
UINT id = mmenu->GetMenuItemID(pos);
//change menu text as 'Stop Capture'
mmenu->ModifyMenu(id, MF_BYCOMMAND, id, _T("Stop Capture(&S)"));
IsCapture = TRUE;
}
}
}
CChildFrame::FindMenuItem(CMenu* Menu, LPCTSTR MenuString)
{
ASSERT(Menu);
ASSERT(::IsMenu(Menu->GetSafeHmenu()));
int count = Menu->GetMenuItemCount();
for (int i = 0; i < count; i++)
{
CString str;
if (Menu->GetMenuString(i, str, MF_BYPOSITION) && str.Compare(MenuString) == 0)
return i;
}
return -1;
}
Related
I have a CTabView with one of the tabs a CRichEditView. Rich text is added to the control and shows fine. If I select text within the CRichEditView the toolbar edit items work fine (for example, copy highlights, and if I click on it, it copies to the clipboard). However, I found that if I selected text and right click there is no context menu with a CRichEditView like there was with CEditView. Searching the Internet, I found an implementation for CRichEditView::GetContextMenu() to try and use. It first had an assert failure because the CDocument is not a rich text type, so for testing, I removed it (commented out below) and ended up with the following:
HMENU CMyRichView::GetContextMenu(WORD seltyp, LPOLEOBJECT lpoleobj, CHARRANGE* lpchrg)
{
// TODO: Add your specialized code here and/or call the base class
/*
CRichEditCntrItem* pItem = GetSelectedItem();
if (pItem == NULL || !pItem->IsInPlaceActive())*/
{
CMenu menuText;
menuText.LoadMenu(IDR_CONTEXT_EDIT_MENU);
CMenu* pMenuPopup = menuText.GetSubMenu(0);
menuText.RemoveMenu(0, MF_BYPOSITION);
return pMenuPopup->Detach();
}
}
Where the IDR_CONTEXT_EDIT_MENU is:
IDR_CONTEXT_EDIT_MENU MENU
BEGIN
POPUP "edit"
BEGIN
MENUITEM "&Copy\tCtrl+C", ID_EDIT_COPY
END
END
Now when I right-click I see the context menu. However, when I choose "copy", nothing happens. So I mapped the ID_EDIT_COPY so I could set a break point to see if it was called.
void CMyRichView::OnEditCopy()
{
// TODO: Add your command handler code here
ASSERT_VALID(this);
GetRichEditCtrl().Copy();
}
It's not if the context item is used, but it is if the toolbar is used.
What am I missing and doing wrong?
TIA!!
If the message goes to CTabView then add CTabView::OnEditCopy handler.
Otherwise, you can override PreTranslateMessage as shown below, this will make sure the message is sent to CMyRichEditView::OnEditCopy.
BOOL CMyRichEditView::PreTranslateMessage(MSG *msg)
{
if(msg->message == WM_CONTEXTMENU || msg->message == WM_RBUTTONDOWN)
{
CMenu menu;
menu.LoadMenu(IDR_CONTEXT_EDIT_MENU);
int c = menu.GetMenuItemCount();
CMenu* popup = menu.GetSubMenu(0);
popup->TrackPopupMenu(0, msg->pt.x, msg->pt.y, this, NULL);
return TRUE;
}
return CRichEditView::PreTranslateMessage(msg);
}
Here is the piece of code that gives me a problem :
void CMainFrame::DisplayActionsPopupMenu()
{
// get "Actions" menu
wxMenuBar* pMenuBar = GetMenuBar();
ASSERT(pMenuBar != NULL);
int nIndex = pMenuBar->FindMenu("Actions");
ASSERT(nIndex != wxNOT_FOUND);
wxMenu *pMenuActions = pMenuBar->GetMenu(nIndex);
ASSERT(pMenuActions != NULL);
// display a popup menu for actions
PopupMenu(pMenuActions);
}
What I try to do here is to display a popupmenu when right clicking and I want it to be the same as the second menu in the menubar of my project.
It worked when I compiled with wxWidgets v2.8
Now I tried with v3.0 and here is the error:
../src/common/menucmn.cpp(715): assert "!IsAttached()" failed in SetInvokingWindow(): menus attached to menu bar can't have invoking window
What should I do to fix this?
I think a more robust solution than the existing answer consisting in detaching and attaching back the menu would be to just create a new menu instead, e.g. something like this:
std::unique_ptr<wxMenu> CreateActionsMenu() { ... }
// In your frame ctor or wherever you initialize your menu bar.
MyFrame::MyFrame() {
wxMenuBar* const mb = new wxMenuBar;
mb->Append(CreateActionsMenu().release(), "&Actions");
SetMenuBar(mb);
}
// In your event handler function showing the popup menu.
void MyFrame::OnShowPopup(wxCommandEvent&) {
auto menu = CreateActionsMenu();
PopupMenu(menu.get());
}
Creating a menu is relatively fast and there should be no problem doing it just before showing it (although, of course, you could also cache it for later if it's really huge or otherwise expensive to construct).
Finally I found that with the >3.0 wxWidgets version, you can't get elements from the wxMenuBar which is attached to your frame. So you have to temporarly unattach and reattach it.
Here is how you would so:
1 - Initialize the new wxMenu with the MenuBar. In my case:
wxMenuBar* pMenuBar = GetMenuBar();
ASSERT(pMenuBar != NULL);
cout<<pMenuBar->IsAttached()<<endl;
int nIndex = pMenuBar->FindMenu("Actions");
ASSERT(nIndex != wxNOT_FOUND);
wxMenu *pMenuActions = pMenuBar->GetMenu(nIndex);
2 - Check if it's attached:
if(pMenuActions->IsAttached()){
pMenuActions->Detach();
}
3 - When your done, reAttach the wxMenu to the wxMenuBar
pMenuActions->Attach(pMenuBar);
I have an application with a ribbon UI. In this UI a button exists with a menu attached to it. What I wish to do is access the menu from the button handler to dynamically add and remove menu items.
void
CMyScrollView::OnMenuButtonHandler ()
{
// TODO: Add your command handler code here
CMFCRibbonBar *pRibbon = ((CMDIFrameWndEx*)GetTopLevelFrame())GetRibbonBar()
// Control ID_BTN_EDIT_MENU
// This where I would like to isolate and vary menu contents
}
In the CMainFRame window create a handler for the AFX_WM_ON_BEFORE_SHOW_RIBBON_ITEM_MENU message (ON_REGISTERED_MESSAGE).
Check for the Id of the button. Remove all previous items and add the one, you want.
LRESULT CMainFrame::OnBeforeShowRibbonItemMenu(WPARAM,LPARAM lp)
{
CMFCRibbonBaseElement *pElement = reinterpret_cast<CMFCRibbonBaseElement*>(lp);
// Try to get our menu button
switch (pElement->GetID())
{
case ID_RIBBON_DROPDOWN_BUTTON:
{
CMFCRibbonButton *pButton = DYNAMIC_DOWNCAST(CMFCRibbonButton, pElement);
if (pButton)
{
// MY_LIST copntains members with the ID and the text: m_uiCmdId, m_strTitle
const MY_LIST &list = ....;
if (list.size()!=0)
{
pButton->RemoveAllSubItems();
for (it = list.begin(); it!=list.end(); ++it)
pButton->AddSubItem(new CSomeKindOfRibbonButton(it->m_uiCmdId, it->m_strTitle));
}
}
...
Sorry for the newbie question, but can anyone point me at sample code that illustrates the use of the CMFCMenuButton? The Microsoft help refers to "New Controls samples", but these samples seem to be in the Visual Studio 2008 "Feature Pack", and this refuses to install on my system since I'm running VS 2013 and don't have VS 2008. I haven't been able to find the samples as stand-alone code.
To be specific, I have a dialog bar in which I want a button labelled Save with drop-down options of Save All and Save Visible (with Save All the default). But any working code would at least get me started.
Declare data members:
CMFCMenuButton m_button_menu;
CMenu m_menu;
Also add the button's id to message map and data exchange:
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_BN_CLICKED(IDC_MFCMENUBUTTON1, OnButtonMenu)
...
END_MESSAGE_MAP
void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_MFCMENUBUTTON1, m_button_menu);
}
Define:
BOOL CMyDialog::OnInitDialog()
{
CDialogEx::OnInitDialog();
//...
m_menu.LoadMenu(IDR_MENU1);
m_button_menu.m_hMenu = m_menu.GetSubMenu(0)->GetSafeHmenu();
return TRUE;
}
Where IDR_MENU1 is a regular menu bar and we get its first submenu. For example:
IDR_MENU1 MENU
BEGIN
POPUP "Dummy"
BEGIN
MENUITEM "&Item1", ID_FILE_ITEM1
MENUITEM "&Item2", ID_FILE_ITEM2
END
END
If button's drop-down arrow is clicked, a popup menu appears, menu result is passed to OnButtonMenu. If left side of button is clicked, then OnButtonMenu is called directly, without showing a popup menu.
void CMyDialog::OnButtonMenu()
{
CString str;
switch (m_button_menu.m_nMenuResult)
{
case ID_FILE_ITEM1:
str = L"first menu item clicked";
break;
case ID_FILE_ITEM2:
str = L"second menu item clicked";
break;
default:
str = L"Button click (popup menu did not appear, or menu ID is not handled)";
break;
}
MessageBox(str);
}
** When working with docking controls, dialog bars, etc. MFC may run its own subclass, I don't think DoDataExchange gets called. m_button_menu could be invalid. GetDlgItem can be used to find the correct pointer:
CMFCMenuButton* CMyDlgBar::GetButtonMenu()
{
CMFCMenuButton* pButton = &m_button_menu;
if (!IsWindow(pButton->m_hWnd))
pButton = (CMFCMenuButton*)GetDlgItem(IDC_MFCMENUBUTTON1);
return pButton;
}
Everywhere else we use GetButtonMenu() instead of m_button_menu. For example:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
//...
m_dlgbar.Create(...);
m_dlgbar.m_menu.LoadMenu(IDR_MENU1);
m_dlgbar.GetButtonMenu()->m_hMenu = m_dlgbar.m_menu.GetSubMenu(0)->GetSafeHmenu();
return 0;
}
void CMainFrame::OnButtonMenu()
{
CString str;
switch (GetButtonMenu()->m_nMenuResult)
...
}
What if the Drop-Down Arrow does not show?
Then read the answer here that explains the changes needed to your RC file.
firstly let me describe what I have:
scenario: CMFCMenuButton, loaded with a CMenu, in a dialog test: click on an item of the menuresult: the message map will get the ID of the CMFCMenuButton and not the ID of the menu
how to get the actual menu ID clicked: use CMFCMenuButton::m_nMenuResultThe idea is that I want to have menu items and buttons in this dialog, and there would be buttons that share IDs with the menu items.So in the handler that I've created for the menu button I can get that m_nMenuResult and send it to the dialog or do whatever I want, but that doesn't seem to be how the CMFCMenuButton should work. What is the correct way of doing it?
CodeHere follows an example on how you can reproduce this.
I've used ON_COMMAND_RANGE also with IDC_MFCMENUBUTTON1 just to reuse the code for the OnMenu function
void CRepositionDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_MFCMENUBUTTON1, m_cmfcMenuButton);
}
BEGIN_MESSAGE_MAP(CRepositionDlg, CDialog)
ON_COMMAND_RANGE(IDC_MFCMENUBUTTON1,IDC_MFCMENUBUTTON1,OnMenu)
ON_COMMAND_RANGE(IDC_MENU1, IDC_MENU11, OnMenu)
END_MESSAGE_MAP()
// CRepositionDlg message handlers
afx_msg void CRepositionDlg::OnMenu(UINT nID)
{
CString csMessage;
csMessage.Format(L"OnMenu(%d)",nID);
AfxMessageBox(csMessage);
if(nID == IDC_MFCMENUBUTTON1)
{
OnMenu(m_cmfcMenuButton.m_nMenuResult);
}
}
BOOL CRepositionDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
CMenu* pMenu = new CMenu;
pMenu->CreatePopupMenu();
for(int i = IDC_MENU1; i <= IDC_MENU11; i++)
{
CString csMenu;
csMenu.Format(L"menu %d",i);
pMenu->AppendMenuW(MF_STRING,i,csMenu);
}
m_cmfcMenuButton.m_hMenu = pMenu->GetSafeHmenu();
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
If you code a handler for BN_CLICKED for the menu button, it will respond with 0 for m_nMenuResult if the click is on the button, or, m_nMenuResult will contain the ID of the menu item selected. If that's not what you wanted, I think you're fighting against the way the button works. Your only other option would be to create your own class to represent a menu button and add the behavior you want.