How to manually show CMFCToolBarComboBoxButton sub-menu? - c++

Standard behaviour for CMFCToolBarComboBoxButton is to have a clickable button plus a drop-down arrow for displaying a submenu. I want to show the submenu independently of where the click was made. How can I do it?
My code to create the button is, more or less, the following (it has been extracted from a larger project, so I apologize for any missing not-too-important piece of code):
// In class declaration:
CMenu m_menu;
CMFCToolBar m_toolbar;
// Where toolbar initialization takes place:
m_menu.CreateMenu();
// ... populate menu
// ID_BUTTON is the ID in the resource file for the toolbar button, 0 is the index for the button icon
CMFCToolBarMenuButton button(ID_BUTTON, m_menu.GetSafeHmenu(), 0);
m_toolbar.ReplaceButton(ID_BUTTON, button);
I've been looking around for awhile and cannot find a related answer.

The solution happened to be very straightforward, just call the OnClick function of the CMFCToolBarComboBoxButton button from its associated ON_COMMAND.
// ... message map
ON_COMMAND(ID_BUTTON, OnToolbarMenuButtonClicked)
// ...
void MyWnd::OnToolbarMenuButtonClicked()
{
const int index = m_toolbar.CommandToIndex(ID_BUTTON);
auto button = (CMFCToolBarComboBoxButton*)m_toolbar.GetButton(index);
button->OnClick(NULL, TRUE);
}
This behaviour is not documented and, contrary to what common sense told me, it doesn't create an infinite recursive call. It seems that the "main" button is still controlled by CMFCToolBarButton, while just the "arrow-button" is controlled by the CMFCToolBarComboBoxButton.
PS: obviously, and out of the scope of the question, the OnToolbarMenuButtonClicked can be used for a very different purpose, such as the default action while the sub-menu contains other less-frequent options.

Related

How to show/hide a Toolbar (the ENTIRE bar, not an individual button) dynamically in MFC?

I created an SDI app with a single menu and a single toolbar in MFC:
I am supposed to show/hide the toolbar dynamically via this code:
...
m_wndToolBar.ShowWindow(SW_HIDE);
...
It did visually hide the toolbar but left a blank, seemingly still working space in the dock region that blocks the main view repositioning. And, when I try to drag the menu, it can dock to the top and bottom line just like the toolbar is there.
I must have missed some necessary steps. So how can I "really" remove the toolbar and raise the main view close to the menu?
Assuming you are using a (relatively) recent version of MFC, and that your m_wndToolBar member is a CMFCToolBar (or derived therefrom), then you should use the ShowPane() member function, rather than the more general ShowWindow() member. Using the former allows the Framework to make the required adjustments to the docking system.
From the linked document:
Call this method instead of the CWnd::ShowWindow when showing or
hiding dockable panes.
(Note that a CMFCToolBar is derived from CPane via the CMFCBaseToolBar class.)
In your call to ShowPane(), the first argument will be TRUE to show the toolbar or FALSE to hide it; the other two arguments will most likely be FALSE, as you want to readjust the docking layout immediately and you generally don't want to activate a toolbar.
So:
//...
m_wndToolBar.ShowPane(FALSE, FALSE, FALSE); // Hide toolbar
//...
Or:
//...
m_wndToolBar.ShowPane(TRUE, FALSE, FALSE); // Show toolbar
//...
Note also that ShowPane() (or, rather, a version of it) is called by the Framework's default handler for a show/hide menu item with the ID of any toolbar (which toggles its visibility); from "afxframewndex.cpp":
BOOL CFrameWndEx::OnPaneCheck(UINT nID)
{
ASSERT_VALID(this);
CBasePane* pBar = GetPane(nID);
if (pBar != NULL)
{
ShowPane(pBar, (pBar->GetStyle() & WS_VISIBLE) == 0, FALSE, FALSE);
return TRUE;
}
return FALSE;
}

Call button click function from grandchild

I'm creating my first C++ wxWidgets application. I'm trying to create some kind of split button where the options are displayed in a grid. I have a custom button class which, when right-clicked on, opens a custom wxPopupTransientWindow that contains other buttons.
When I click on the buttons in the popup, I want to simulate a left click on the main button. I'm trying to achieve this through events, but I'm kinda confused.
void expandButton::mouseReleased(wxMouseEvent& evt)
{
if (pressed) {
pressed = false;
paintNow();
wxWindow* mBtn = this->GetGrandParent();
mBtn->SetLabel(this->GetLabel());
mBtn->Refresh();
wxCommandEvent event(wxEVT_BUTTON);
event.SetId(GetId());
event.SetEventObject(mBtn);
mBtn-> //make it process the event somehow?
wxPopupTransientWindow* popup = wxDynamicCast(this->GetParent(), wxPopupTransientWindow);
popup->Dismiss();
}
}
What is the best way to do this?
You should do mBtn->ProcessWindowEvent() which is a shorter synonym for mBtn->GetEventHandler()->ProcessEvent() already mentioned in the comments.
Note that, generally speaking, you're not supposed to create wxEVT_BUTTON events from your own code. In this particular case and with current (and all past) version(s) of wxWidgets it will work, but a cleaner, and guaranteed to also work with the future versions, solution would be define your own custom event and generate it instead.

myComboBox->Dismiss(); not working under wxEVT_TEXT and wxEVT_COMBOBOX_DROPDOWN

My objective:
During wxEVT_COMBOBOX_DROPDOWN load up a choiceDialog and set the choice as combo box value and finally close the combobox using dismiss().
What I tried:
void Class_Scheduler_FNFrame::myComboBoxDropdown(wxCommandEvent& event)
{
SingleChoiceDialog1->ShowModal();
int i = SingleChoiceDialog1->GetSelection();
myComboBox->SetValue(wxString::Format(("%s"), myWxStringArray[i]));
myComboBox->Dismiss();
}
The value got changed but the dismiss(); is not working.
So I tried moving dismiss to wxEVT_TEXT.
void Class_Scheduler_FNFrame::myComboBoxTextUpdated(wxCommandEvent& event)
{
testLabel->SetLabel("This should have worked");
myComboBox->Dismiss();
}
Dismiss() is not working in wxEVT_TEXT as well. But when I type onto the combobox, after triggering wxEVT_COMBOBOX_DROPDOWN, dismiss() is working through wxEVT_TEXT .
Help!
You're trying to do something rather weird: have you ever seen a modal dialog show while the combobox dropdown is opened? I haven't and, generally speaking, there should be only a single modal element shown at each moment.
So why do you have this objective? What is the point of showing the dialog whenever a combobox is opened? It doesn't even seem like you're using it like a combobox at all. Maybe you're actually after some kind of wxFilePickerCtrl-like behaviour, i.e. a small button showing the dialog near a text control? Or maybe you want to use wxComboCtrl instead?
Anyhow, it should be possible to do what you want by postponing showing the dialog using CallAfter(), but I'm almost sure that you actually don't want to do that anyhow.

How do you close a QMenu when its QWidgetAction is triggered?

I have a QMenu for which I've created a QColorModel action widget (It's effectively just a QStandardItemModel). My desired behavior is that when a user clicks one of the colors in the model, that the action should trigger, and the menu close. However, it doesn't seem to do that, even when I trigger the action manually.
I've tried manually hiding the menu, but it's a kludge because it won't hide parent menus which th menu may be attached to.
Here's the relevant section of code:
// color menu
m_colorMenu = new QMenu("color", this);
m_colorView = new QColorView(m_colorMenu);
m_colorViewAction = new QWidgetAction(m_colorMenu);
m_colorViewAction->setDefaultWidget(m_colorView);
m_colorView->setModel(new QStandardColorModel);
connect(m_colorView, &QColorView::clicked, [&](QModelIndex index)
{
QColor color = qvariant_cast<QColor>(index.data(Qt::DecorationRole));
if (m_pen.color() != color)
{
m_pen.setColor(color);
drawIcon();
drawColorIcon();
update();
}
//this->hide(); // kludge, didn't close all parent menus
m_colorViewAction->trigger(); // doesn't seem to cause menu closure
});
m_colorMenu->addAction(m_colorViewAction);
EDIT
I've also tried adding something to the effect of:
QMenu* menu = m_colorMenu;
do
{
menu->close();
menu = dynamic_cast<QMenu*>(menu->parent());
} while (menu);
but it also is fragile/kludgey because it assumes a) all widgets are properly parented, and b) that all the parents are actually supposed to be menus. In my case, they aren't.
If the containing menus aren't in the parentage tree, and the menu you want to close isn't the top level menu, there is no easy way to do this. That said, there is:
THE NUCLEAR OPTION
Adding this to the end of the lambda function
auto topLevelWidgets = qApp->topLevelWidgets();
for (auto widget : topLevelWidgets)
{
QMenu* menu = dynamic_cast<QMenu*>(widget);
if (menu)
{
menu->close();
}
}
will cause ALL top level menus to close once the action is triggered. This is a relatively OK way to accomplish what you want because:
one of the top level menus will contain the menu in question, and
never say never, but I can't think of a single case where you would have (or want) more than one menu open at a time, so most likely the only open menu tree you would close is the intended one.

MFC - Changing dialog item focus programmatically

I have a Modeless dialog which shows a bunch of buttons; some of these are customized to draw stuff with GDI.
Now, when the user clicks on a customized one under certain conditions, a message box appears to alert user of the error and this is fine.
The problem is that after accepting the Message Box (showed as MB_ICON_ERROR), everywhere I click in the dialog, I always get the error message as if the whole dialog send the message to the customized button and the only way to get rid this is to press tab and give the focus to another control.
This is a strange behaviour and knowing why happens wouldn't be bad, but a simple workaround for now should do the job.
Since the moment that is probably a matter of focus, I've tried to set it on another control (in the owner dialog) by doing:GetDlgItem( IDC_BTN_ANOTHER_BUTTON )->SetFocus();
and then, inside the customized control by adding:KillFocus( NULL );but had no results.
How should I use these functions?
Thanks in advance.
PS: if I comment the AfxMessageBox, the control does not show this bizarre behaviour.
EDITI'll show some code as requested.
// This is where Message Box is popping out. It is effectively inside the dialog code.
void CProfiloSuolaDlg::ProcessLBtnDownGraphProfilo(PNT_2D &p2dPunto)
{
// m_lboxProfiles is a customized CListBox
if(m_lboxProfiles.GetCurSel() == 0)
{
// This profile cannot be modified.
/*
CString strMessage;
strMessage.Format( _T("Default Profile cannot be edited.") );
AfxMessageBox( strMessaggio, MB_ICONERROR );
*/
return;
}
// Selecting a node from sole perimeter.
SelectNodo(p2dPoint);
}
Actually, the message is commented to keep the dialog working.
// This is inside the customization of CButton
void CMyGraphicButton::OnLButtonDown(UINT nFlags, CPoint point)
{
PNT_2D p2dPunto;
CProfiloSuolaDlg* pDlg = (CProfiloSuolaDlg*)GetParent();
m_pVD->MapToViewport(point,p2dPunto);
switch(m_uType)
{
case GRF_SEZIONE:
pDlg->ProcessLBtnDownGraphProfilo(p2dPunto);
break;
case GRF_PERIMETRO:
pDlg->ProcessLBtnDownGraphPerimetro(p2dPunto);
break;
}
CButton::OnLButtonDown(nFlags, point);
}
Since you are handling the button down event in the button handler for the custom control, you don't need to call the base class. Just comment out CButton::OnLButtonDown(nFlags, point).