How to programmatically show dropdown-menu of ribbon button? - c++

I have ribbon button with a set of sub-items added. Such items are displayed when the user clicks on the tiny arrow below the button. I'd like to display such dropdown-menu when the button itself is clicked. How can I do that?
My initial idea was to programmatically show the menu when the user clicks the button. I've been able to do the same on toolbars (here) but using a similar solution on ribbons creates an infinite recursion:
// ...
ON_COMMAND(ID_RIBBON_BUTTON, &MainFrame::OnButtonClicked)
// ...
CMFCRibbonPanel *panel = /* initialization */
CMFCRibbonButton *button = new CMFCRibbonButton(ID_RIBBON_BUTTON, "Caption");
panel->Add(button);
CMFCRibbonButton *item1 = new CMFCRibbonButton(ID_RIBBON_BUTTON, "Item 1");
button->AddSubItem(item1);
CMFCRibbonButton *item2 = new CMFCRibbonButton(ID_RIBBON_BUTTON, "Item 2");
button->AddSubItem(item2);
// ...
void MainFrame::OnButtonClicked()
{
if (auto button = static_cast<CMFCRibbonButton *>(m_ribbons.wndRibbonBar.FindByID(ID_RIBBON_BUTTON))) {
// button->OnClick({}); // <- causes infinite recursion
// What to do here?
}
}

The easiest way I've found so far is to use the protected method CMFCRibbonButton::OnShowPopupMenu. It implies deriving the CMFCRibbonButton class and changing the visibility of the method:
#include <afxribbonbutton.h>
class CMyMFCRibbonButton : public CMFCRibbonButton {
public:
using CMFCRibbonButton::CMFCRibbonButton;
virtual void OnShowPopupMenu() override {
CMFCRibbonButton::OnShowPopupMenu();
}
};
// ...
ON_COMMAND(ID_RIBBON_BUTTON, &MainFrame::OnButtonClicked)
// ...
CMFCRibbonPanel *panel = /* initialization */
CMyMFCRibbonButton *button = new CMyMFCRibbonButton(ID_RIBBON_BUTTON, "Caption");
panel->Add(button);
CMFCRibbonButton *item1 = new CMFCRibbonButton(ID_RIBBON_BUTTON, "Item 1");
button->AddSubItem(item1);
CMFCRibbonButton *item2 = new CMFCRibbonButton(ID_RIBBON_BUTTON, "Item 2");
button->AddSubItem(item2);
// ...
void MainFrame::OnButtonClicked()
{
if (auto button = static_cast<CMyMFCRibbonButton*>(m_ribbons.wndRibbonBar.FindByID(ID_RIBBON_BUTTON))) {
button->OnShowPopupMenu();
}
}

Related

Qt: propagate mouse clicks through a transparent widget

I'm trying to create a widget which, when shown, it will intercept any mouse clicks, process them, but then forward the click to the widget that was under the mouse when it was clicked. I've created a class that represents this widget; all it does for now is capture mouse release events, hide itself and post the event to the widget the click was intended for using QApplication::postEvent:
class Overlay : public QWidget
{
Q_OBJECT
public:
Overlay(QWidget* parent)
: QWidget(parent){}
protected:
void mouseReleaseEvent(QMouseEvent* event)
{
hide();
auto child = parentWidget()->childAt(event->pos()); // get the child widget the event was intended for
auto e = new QMouseEvent(*event);
if(child) QCoreApplication::postEvent(child, e); // why doesn't this have any effect?
event->accept();
}
void paintEvent(QPaintEvent* event)
{
QPainter p(this);
p.fillRect(rect(), QColor(0,0,0,150));
}
};
Unfortunately, the call to postEvent doesn't seem to have any effect at all. Here is an example where I'm setting up some child widgets; clicking on any widget will print something out:
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr)
{
m_overlay = new Overlay(this);
m_overlay->hide();
auto button = new QPushButton("Add overlay");
auto button2 = new QPushButton("PushButton");
auto list = new QListWidget();
list->addItems(QStringList() << "item1" << "item2" << "item3");
connect(list, &QListWidget::itemClicked, [](const QListWidgetItem* item)
{
qDebug() << item->text();
});
connect(button2, &QPushButton::clicked, []()
{
qDebug() << "Button clicked";
});
connect(button, &QPushButton::clicked, [this]()
{
m_overlay->setGeometry(rect());
m_overlay->raise();
m_overlay->show();
});
auto layout = new QVBoxLayout(this);
layout->addWidget(list);
layout->addWidget(button);
layout->addWidget(button2);
}
public:
~Widget(){}
private:
Overlay* m_overlay;
};
I've confirmed that clicks on the overlay are captured and the overlay is hidden as intended but the event is not propagated to the underlying widget. Using QApplication::sendEvent doesn't work either. What am I missing?
Using Qt 5.15.1 on MacOS 11.2.3. Thanks

How to convert QMenu to QMenuBar?

I have QMenu, which contains submenus:
QMenu menu;
// Add some submenus
menu.addMenu(new QMenu("1", menu));
menu.addMenu(new QMenu("2", menu));
menu.addMenu(new QMenu("3", menu));
I want to move these submenus from QMenu to QMenuBar:
QMenuBar* menubar = convertFromQMenu(&menu);
Here is how I think the implementation of convertFromQMenu might look like:
QMenuBar* convertFromQMenu(QMenu* menu) {
QMenuBar *menubar = new QMenuBar();
/*
for(QMenu* menu: menu->menus()) {
menu.setParent(menubar);
menubar.addMenu(menu);
}
*/
return menubar;
}
However, the commented code does not compile.
How to fix this?
You don't need that kind of 'conversion'. If you read carefully from the Qt official doc about QMenuBar and addMenu() member, you can easily add your QMenu to your QMenuBar:
QMenu menu;
// Add some menus
menu.add (new QMenu("1", menu));
menu.add (new QMenu("2", menu));
menu.add (new QMenu("3", menu));
QMenuBar menubar;
menubar.addMenu(&menu);
If you want to use the QMainWindow menu bar, the doc states :
In most main window style applications you would use the menuBar() function provided in QMainWindow, adding QMenus to the menu bar and adding QActions to the pop-up menus.
Example (from the Menus example):
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(newAct);
Cause
for(QMenu* menu: menu->menus()) {
QMenu does not have a menus method.
Solution
The path to get to where you want is a little bit longer:
Use QWidget::actions to get a list of the actions, added to the menu
Get the QMenu associated with each action using QAction::menu
Now you can add the menu to the QMenuBar.
Note: Do not change the parenting of the menus and submenus.
Example
Based on your code, I would suggest you to implement convertFromQMenu like this:
QMenuBar *MainWindow::convertFromQMenu(QMenu *menu)
{
auto *menubar = new QMenuBar(this);
foreach (QAction *act, menu->actions())
if (act->menu())
menubar->addMenu(act->menu());
menu->deleteLater();
return menubar;
}
Here is how to test the suggested implementation:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
auto *menu = new QMenu("Menu", this);
// Add some menus
menu->addMenu(new QMenu("Menu 1", this));
menu->addMenu(new QMenu("Menu 2", this));
menu->addSeparator();
auto *submenu = new QMenu("Menu 3", this);
submenu->addActions(QList<QAction *>({new QAction("Action 1", this),
new QAction("Action 2", this),
new QAction("Action 3", this)}));
menu->addMenu(submenu);
setMenuBar(convertFromQMenu(menu));
setCentralWidget(new QWidget(this));
resize(300, 200);
}
I took the liberty to extend this example to switch between compact and extended form of the menus in the menu bar (not shown here). The full code is available on GitHub.
Result
As written, the given example produces the following result:

How to make QMenu Item checkable in QT

How to give Qmenu item checkable using QT
QMenu *preferenceMenu = new QMenu();
preferenceMenu = editMenu->addMenu(tr("&Preferences"));
QMenu *Mode1 = new QMenu();
Mode1 = preferenceMenu->addMenu(tr("&Mode 1"));
Mode1->addAction(new QAction(tr("&Menu1"), this));
QMenu *Mode2 = new QMenu();
Mode2 = preferenceMenu->addMenu(tr("&Mode 2"));
Mode2->addAction(new QAction(tr("&Menu2"), this));
Mode2->addAction(new QAction(tr("&Menu3"), this));
On QAction I called slot "slotActionTriggered(QAction* actionSelected)"
void csTitleBar::slotActionTriggered(QAction* actionSelected)
{
actionSelected->setChecked(true);
}
How to show small TICK in the selected Menu# so that user can know which is selected
Currently I am able to change to all Menu#, but I need to show a small tick on menu so that the selected one can be easly Identified
Small example:
cmainwindow.h
#ifndef CMAINWINDOW_H
#define CMAINWINDOW_H
#include <QMainWindow>
#include <QPointer>
class CMainWindow : public QMainWindow
{
Q_OBJECT
public:
CMainWindow(QWidget *parent = 0);
~CMainWindow();
private slots:
void slot_SomethingChecked();
private:
QPointer<QAction> m_p_Act_Button1 = nullptr;
QPointer<QAction> m_p_Act_Button2 = nullptr;
};
#endif // CMAINWINDOW_H
cmainwindow.cpp
#include "cmainwindow.h"
#include <QtWidgets>
#include <QDebug>
CMainWindow::CMainWindow(QWidget *parent)
: QMainWindow(parent)
{
m_p_Act_Button1 = new QAction("Super Button 1", this);
m_p_Act_Button1->setCheckable(true);
m_p_Act_Button1->setChecked(true);
connect(m_p_Act_Button1, SIGNAL(triggered()), this, SLOT(slot_SomethingChecked()));
m_p_Act_Button2 = new QAction("Super Button 2", this);
m_p_Act_Button2->setCheckable(true);
m_p_Act_Button2->setChecked(true);
connect(m_p_Act_Button2, SIGNAL(triggered()), this, SLOT(slot_SomethingChecked()));
QMenu *p_menu = menuBar()->addMenu("My Menu");
p_menu->addAction(m_p_Act_Button1);
p_menu->addAction(m_p_Act_Button2);
}
CMainWindow::~CMainWindow() { }
void CMainWindow::slot_SomethingChecked()
{
if(!m_p_Act_Button1 || !m_p_Act_Button2) {return;}
qDebug() << "Hi";
if(m_p_Act_Button1->isChecked())
{
qDebug() << "The action 1 is now checked";
}
else
{
qDebug() << "The action 1 is now unchecked";
}
if(m_p_Act_Button2->isChecked())
{
qDebug() << "The action 2 is now checked";
}
else
{
qDebug() << "The action 2 is now unchecked";
}
}
Modern connection syntax is preferable.
Checkable items can also be used on context menu as follow :
void MyWidget::initContextMenu()
{
if( _contextMenu)
{
delete _contextMenu;
}
_contextMenu = new QMenu(this);
auto action1 = new QAction("Action Item 1", &_contextMenu);
action1->setCheckable(true);
action1->setChecked(getMyProperty1());
connect(action1 , &QAction::triggered, this, &MyWidget::setMyProperty1);
_contextMenu->AddAction( action1 );
}
void MyWidget::contextMenuEvent(QContextMenuEvent *event)
{
initContextMenu();
_contextMenu.popup(
this->viewport()->mapToGlobal(
event->pos()));
}
bool MyWidget::setMyProperty1(bool state)
{
_myProperty1 = state;
emit MyPropertyChanged(_myProperty);
}

Combobox in CMFCMenuBar does not open

How to populate help combo box
class MyFrame : public CFrameWnd {
...
CMFCMenuBar m_menuBar;
...
};
int MyFrame::OnCreate(LPCREATESTRUCT lpCreateStruct )
{
int res = CFrameWnd::OnCreate(lpCreateStruct);
m_menuBar.Create(this);
m_menuBar.EnableHelpCombobox(1, _T("Test"), 150 );
m_menuBar.CreateFromMenu(hMenu,FALSE,TRUE);
CMFCToolBarComboBoxButton* combo = m_menuBar.GetHelpCombobox();
combo->AddItem("Item 1");
combo->AddItem("Item 2");
combo->AddItem("Item 3");
combo->AddItem("Item 4");
combo->EnableWindow(true);
combo->SelectItem(2,FALSE);
combo->SetCenterVert();
combo->SetDropDownHeight(150);
return res;
}
The item text "Item 3" which was selected is shown combo field but nothing happnes when I hit the button on dropdown. CMFCToolBarComboBoxButton::AddItem(...) returns valid index and CMFCToolBarComboBoxButton::Count(...) returns 4 as expected.

Adding a custom Authors category to Gtk::AboutDialog class

I was wondering if there was a way to set a custom Authors category in a Gtk::AboutDialog class via gtkmm. I know there are the following methods:
set_artists()
set_authors()
set_documenters()
set_translator_credits()
But I wanted to add a custom category. Right now I have a program that accepts a bunch of plugins, so on startup when it scans for plugins I would like to populate a "Plugins" page on the about screen once you click credits that shows all of the plugin authors' names (removing duplicates of course). The logic is already there, but it looks quite odd adding them to the artists or documenters categories where they certainly do not belong.
Is there an easy way to add a new category besides rolling my own?
Nice question! In GTK 3, this is fairly easy. You have to do some manipulation of the About dialog's internal children, which may change in future releases, so be warned!
I've written a quick-n-dirty example in Vala that does what you want. That was faster for me because I almost never use Gtkmm. It shouldn't be too hard to translate though.
using Gtk;
int main(string[] args)
{
Gtk.init(ref args);
var dialog = new AboutDialog();
// Fetch internal children, using trickery
var box = dialog.get_child() as Box;
Box? box2 = null;
ButtonBox? buttons = null;
Notebook? notebook = null;
box.forall( (child) => {
if(child.name == "GtkBox")
box2 = child as Box;
else if(child.name == "GtkButtonBox")
buttons = child as ButtonBox;
});
box2.forall( (child) => {
if(child.name == "GtkNotebook")
notebook = child as Notebook;
});
// Add a new page to the notebook (put whatever widgets you want in it)
var plugin_page_index = notebook.append_page(new Label("Plugin 1\nPlugin 2"),
new Label("Plugins"));
// Add a button that toggles whether the page is visible
var button = new ToggleButton.with_label("Plugins");
button.clicked.connect( (button) => {
notebook.page = (button as ToggleButton).active? plugin_page_index : 0;
});
buttons.pack_start(button);
buttons.set_child_secondary(button, true);
// Set some other parameters
dialog.program_name = "Test Program";
dialog.logo_icon_name = Gtk.Stock.ABOUT;
dialog.version = "0.1";
dialog.authors = { "Author 1", "Author 2" };
dialog.show_all(); // otherwise the new widgets are invisible
dialog.run();
return 0;
}
In GTK 2, this is much more difficult, although probably not impossible. You have to connect to the Credits button's clicked signal, with a handler that runs after the normal handler, and then get a list of toplevel windows and look for the new window that opens. Then you can add another page to that window's GtkNotebook.
I would suggest doing it a little differently: add a Plugins button to the action area which opens its own window. Then you don't have to go messing around with internal children. Here's another Vala sample:
using Gtk;
class PluginsAboutDialog : AboutDialog {
private Dialog _plugins_window;
private Widget _plugins_widget;
public Widget plugins_widget { get {
return _plugins_widget;
}
set {
var content_area = _plugins_window.get_content_area() as VBox;
if(_plugins_widget != null)
content_area.remove(_plugins_widget);
_plugins_widget = value;
content_area.pack_start(value);
}}
public PluginsAboutDialog() {
_plugins_window = new Dialog();
_plugins_window.title = "Plugins";
_plugins_window.add_buttons(Stock.CLOSE, ResponseType.CLOSE, null);
_plugins_window.response.connect((widget, response) => { widget.hide(); });
var buttons = get_action_area() as HButtonBox;
// Add a button that opens a plugins window
var button = new Button.with_label("Plugins");
button.clicked.connect( (button) => {
_plugins_window.show_all();
_plugins_window.run();
});
button.show();
buttons.pack_start(button);
buttons.set_child_secondary(button, true);
}
public static int main(string[] args) {
Gtk.init(ref args);
var dialog = new PluginsAboutDialog();
// Make a widget for the plugins window
var can_be_any_widget = new Label("Plugin 1\nPlugin 2");
dialog.plugins_widget = can_be_any_widget;
// Set some other parameters
dialog.program_name = "Test Program";
dialog.logo_icon_name = Gtk.Stock.ABOUT;
dialog.version = "0.1";
dialog.authors = { "Author 1", "Author 2" };
dialog.run();
return 0;
}
}