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);
Related
(Update, see original question below)
After doing a bit of digging, I'm basically trying to understand the following; In the context of an MDI application, if a menu (which is associated with a specific CChildWnd) has an MF_OWNERDRAW, why are the ON_WM_MEASUREITEM and ON_WM_DRAWITEM events send to the CMainWnd instead of the CChildWnd?
In my InitInstance, the document template is registered and the associated menu is modified to add the MF_OWNERDRAW:
BOOL CMyApp::InitInstance()
{
// ...
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_CHILDFRAME,
RUNTIME_CLASS(CFooDoc),
RUNTIME_CLASS(CFooWnd),
RUNTIME_CLASS(CFooView)
);
if (pDocTemplate->m_hMenuShared != NULL) {
CMenu* pMenu = CMenu::FromHandle(pDocTemplate->m_hMenuShared);
// Add MF_ONWERDRAW to the items that need it.
pMenu->ModifyMenu([item_id], MF_BYCOMMAND | MF_OWNERDRAW, [item_id]);
}
AddDocTemplate(pDocTemplate);
// ...
}
So, once the document template is registered, the menu associated with the document/frame is modified to add the MF_ONWERDRAW flag to each of the required items (the color selection items in my case).
However, why are the OnMeasureItem and OnDrawItem events send to the CMainWnd and not the CFooWnd? And how can I direct the events to the CFooWnd instead?
The reason I'am asking, if I have 5 different types of documents in my MDI application, each needing custom menus, then the CMainWnd basically becomes a mess of message handling. The logical place for the custom menu logic is in the CChildWnd, not the CMainWnd.
Original question:
I'm doing some work on a very old application (MFC 4.2) and I'm running into a problem with drawing in a menu item.
The original application has a menu to select a color and it actually draws the colors in the menu when opened so it easier for the user to select the color.
The behavior for this implemented in CMainWnd using the OnMeasureItem and the OnDrawItem.
class CMainWnd : public CMDIFrameWnd
{
DECLARE_DYNCREATE(CMainWnd)
protected:
afx_msg void OnMeasureItem(int, LPMEASUREITEMSTRUCT);
afx_msg void OnDrawItem(int, LPDRAWITEMSTRUCT);
DECLARE_MESSAGE_MAP()
};
Then, in the implementation (omitted bits and pieces for brevity):
BEGIN_MESSAGE_MAP(CMainWnd, CMDIFrameWnd)
ON_WM_MEASUREITEM()
ON_WM_DRAWITEM()
END_MESSAGE_MAP()
void CMainWnd::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
lpmis->itemWidth = ::GetSystemMetrics(SM_CYMENU) * 4;
lpmis->itemHeight = ::GetSystemMetrics(SM_CYMENU) * 1;
}
void CMainWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
CDC dc;
dc.Attach(lpdis->hDC);
CBrush* pBrush;
// draw the hover/selection rectangle
pBrush = new CBrush(::GetSysColor((lpdis->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT :
COLOR_MENU));
dc.FrameRect(&(lpdis->rcItem), pBrush);
delete pBrush;
// load a checkbox icon into a bitmap
BITMAP bm;
CBitmap bitmap;
bitmap.LoadOEMBitmap(OBM_CHECK);
bitmap.GetObject(sizeof(bm), &bm);
// if color/item selected then draw the checkbox
if (lpdis->itemState & ODS_CHECKED) {
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap* pOldBitmap = dcMem.SelectObject(&bitmap);
dc.BitBlt(
lpdis->rcItem.left + 4,
lpdis->rcItem.top + (((lpdis->rcItem.bottom - lpdis->rcItem.top) - bm.bmHeight) / bm.bmWidth,
bm.bmHeight,
&dcMem,
0,
0,
SRCCOPY
);
dcMem.SelectObject(pOldBitmap);
}
// draw the actual color bar
pBrush = new CBrush(CPaintDoc::m_crColors[lpdis->itemID - ID_COLOR_BLACK]);
CRect rect = lpdis->rcItem;
rect.DeflateRect(6, 4);
rect.left += bm.bmWidth;
dc.FillRect(rect, pBrush);
delete pBrush;
dc.Detach();
}
What the OnDrawItem does is; it draws a horizontal color bar with a color, prefixed by a check icon if that color is selected and the menu item being hovered over is highlighted by a box being drawn around it.
However, since I'm turning this application into a Multidoc application and I don't really feel that this logic should be in the CMainWnd (since none of the other documents will have this type of menu), but that it should be part of the CChildWnd (which inherits from CMDIChildWnd).
But when I move this logic to that class, when I run the application, I get following message in the console logger:
Warning: unknown WM_MEASUREITEM for menu item 0x0082.
And none of the custom menu behavior seems to work.
so, the question is; How can move the custom behavior of a menu into the frame class of an MDI document rather than having it located in the application main frame?
I figured out a work around. Not ideal but I can understand that this is a quirk in the framework, i.e. the menu seems to be part of the MainWnd so from a technical point of view, that is where the ON_WM_MEASUREITEM and ON_WM_DRAWITEM would be handled.
Anyhow, my work around. Basically capture the events in the MainWnd and then delegate the behaviour to the ChildWnd. The trick here (I guess) is to figure out what ChildWnd to delegate to since in an MDI application there can be any number of different ChildWnd's (each with their own Document and View types).
The work around:
void CMainWnd::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
CMDIChildWnd* pActiveWnd = MDIGetActive();
if(pActiveWnd && pActiveWnd->IsWindowVisible())
{
if(pActiveWnd->IsKindOf(RUNTIME_CLASS(CMyChildWnd))) {
CMyChildWnd* pMyChildWnd = (CMyChildWnd*)pActiveWnd;
CMyChildWnd->DoMeasureItem(nIDCtl, lpmis);
}
}
}
void CMainWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
CMDIChildWnd* pActiveWnd = MDIGetActive();
if(pActiveWnd && pActiveWnd->IsWindowVisible())
{
if(pActiveWnd->IsKindOf(RUNTIME_CLASS(CMyChildWnd))) {
CMyChildWnd* pMyChildWnd = (CMyChildWnd*)pActiveWnd;
CMyChildWnd->DoDrawItem(nIDCtl, lpdis);
}
}
}
Pretty straight forward, in the context of the MainWnd, get a pointer to the active MDI ChildWnd, check if it is active, then check the type by using IsKindOf and RUNTIME_CLASS and if so, voila, delegate the behavior to the ChildWnd. To DoMeasureItem and the DoDrawItem are just public methods implemented on the ChildWnd (see question for details).
I am using QT and QT Creator and compiling with it (mingw compiler for windows). I am trying to create a listbox with some mouse based functions. I have been able to add a context menu to it. Second, when I right click the contextmenu the listbox item gets selected. Third, even if I left click on the item the item gets selected. What I want to do is if an item is selected and I left click on it again, it should deselect the item in the list menu. The issue is if I write my own code, it disables the default action and the the if statement locks up the window and the window crashes. What is the logic I need to do to handle both selection and deselection methods? Seems I am doing something wrong. Here is my code. Any help is welcome.
#include "historicalrequests.h"
#include "ui_historicalrequests.h"
#include "customlistitemdelegate.h"
HistoricalRequests::HistoricalRequests(QWidget *parent) :
QWidget(parent),
ui(new Ui::HistoricalRequests)
{
ui->setupUi(this);
ui->historicalRequestsWidget->setItemDelegate(new CustomListItemDelegate(ui->historicalRequestsWidget));
QListWidgetItem *item = new QListWidgetItem();
item->setData(Qt::DisplayRole, "Title");
item->setData(Qt::UserRole + 1, "21/05/2017 12:12:456");
ui->historicalRequestsWidget->addItem(item);
QListWidgetItem *item2 = new QListWidgetItem();
item2->setData(Qt::DisplayRole, "Workspace");
item2->setData(Qt::UserRole + 1, "Description");
ui->historicalRequestsWidget->addItem(item2);
ui->historicalRequestsWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->historicalRequestsWidget, SIGNAL(customContextMenuRequested(QPoint)), SLOT(customMenuRequested(QPoint)));
}
HistoricalRequests::~HistoricalRequests()
{
delete ui;
}
void HistoricalRequests::customMenuRequested(QPoint pos){
// My customContextMenu click functions here
}
void HistoricalRequests::on_historicalRequestsWidget_clicked(const QModelIndex &index)
{
if(ui->historicalRequestsWidget->item(index.row())){
if((ui->historicalRequestsWidget->currentItem() == ui->historicalRequestsWidget->item(index.row()))){
qWarning("Same item" );
} else {
qWarning("Different item");
//ui->historicalRequestsWidget->item(index.row())->setSelected(true);
}
} else {
// some code here
}
}
From what I understand, all you need to do is set SelectionMode to MultiSelection:
ui->listWidget->setSelectionMode(QAbstractItemView::MultiSelection);
If you need to have selected only one item a time, you can handle it in slot connected to QSelectionModel::selectionChanged signal. There you can deselect previous item with selectionModel->select() method.
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.
So far I have written some simple code for a wxWidgets application, like creating a menu, frame and a few buttons. To follow the process of exiting, I have this function that shows a message box :
int OnExit( )
{
wxMessageBox( "Closing the application", wxOK | wxICON_INFORMATION )
return 0;
}
Closing the application by clicking close ( X ) button shows the message box and then exits. But closing it by clicking the "Quit" menu item doesn't work for me. I have tried copying some code from an old example and from CodeBlocks basic sample code that comes with wxWidgets project, with no luck. Please show me a method of closing the application from the menu item.
Try searching the web for "wxwidgets close window menu":
wxWidgets Hello World Example
In your OnExit function you need to call the Close method as in the example.
// Build: g++ this.cpp -std=gnu++11 $(wx-config --cxxflags --libs core,base)
#include <wx/wx.h>
class CApp : public wxApp
{
public:
bool OnInit() {
// Create the main frame.
wxFrame * frame = new wxFrame(NULL, wxID_ANY, wxT("demo"));
// Add the menubar
wxMenu * menus[] = {new wxMenu, new wxMenu};
wxString labels[] = {wxT("&File"), wxT("&Help")};
frame->wxFrame::SetMenuBar(new wxMenuBar(2, menus, labels));
menus[0]->Append(wxID_EXIT);
// Bind an event handling method for menu item wxID_EXIT.
this->Bind(wxEVT_MENU, [frame](wxCommandEvent &)->void{
frame->Close();
/* 1. method wxWindow::Close
* 2. event type wxEVT_CLOSE_WINDOW
* 3. method wxTopLevelWindow::OnCloseWindow
* 4. method wxTopLevelWindow::Destroy (overriding wxWindow::Destroy)
* 5. op delete
*/
}, wxID_EXIT);
// Enter the message loop.
frame->Centre(wxBOTH);
frame->Show(true);
return true;
}
int OnExit() {
wxMessageBox("Closing the application", wxEmptyString, wxOK | wxICON_INFORMATION);
return this->wxApp::OnExit();
}
};
wxDECLARE_APP(CApp);
wxIMPLEMENT_APP(CApp);
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;
}