Hi all is there any way to automatically expand a QToolbar if there is too many QActions in?
Using Qt version 5.4.1 C++11
Ive tried :ui->mainToolBar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred)
But this only expands it horizontally. I need it to expand vertically like the Expand button does.
Always expanding a toolbar vertically is not possible as far as I know (never seen it). A solution would be to add multiple toolbars. This way, you can arrange them one under the other.
What you can try is to add a custom widget to the toolbar that grows horizontally. This was proposed here by using a QScrollArea... Not sure whether this is exactly what you want, but it may work good enough.
This is how you can make a function to expand/retract a QToolbar. Firstly using a Forloop get all the child widgets from the QToolbar. You can use a Bool to lock to only get the first Widget which is the Expanding button/Action.
bool get_first_action{true};
for(QWidget* widget : ui->myToolBar->findChildren<QWidget*>())
{
if(get_first_action)
{
get_first_action = false;
// This is the expanding action!
m_action_expand = widget;
}
}
Or you can do this which is probably a bit safer.
for(QWidget* widget : ui->myToolBar->findChildren<QWidget*>())
{
if(widget->objectName() == "qt_toolbar_ext_button")
{
// This is the expanding action!
m_action_expand = widget;
}
}
Once you have the sneaky expanding action assign it to a member varible
// Make sure to initialize this in the constructor!
// m_action_expand = new QWidget(this // parent)
QWidget* m_action_expand;
Now create a handy function with a good name;
void MainWindow::forceToolbarExpand()
{
// Grab the position of the expanding action/widget
QPointF pos(m_action_expand->pos());
// Create a fake/dummy event that replicates the mouse button press
QMouseEvent event_press(QEvent::MouseButtonPress, pos, Qt::LeftButton,0, 0);
// Create a fake/dummy event that replicates the mouse button release
QMouseEvent event_release(QEvent::MouseButtonRelease, pos, Qt::LeftButton,0, 0);
// These two events together will fire the QAction::Toggled signal.
// Make sure to send the events!
QApplication::sendEvent(m_action_expand, &event_press);
QApplication::sendEvent(m_action_expand, &event_release);
}
And there we have it your QToolbar, if it can be expanded/retracted now will when you call this function. I'm not too sure if you can directly Moc/fake the toggled event but you can try it. I know this method works so yeah.
Related
I have a simple class based on QTreeWidget. In some cases (when value one of columns updated), I need to repaint it. I have a function which invokes when I need to update my widget:
void TreeWidget::updated()
{
/* some actions with cells */
/* here need to repaint widget */
this->update();
/* also I'm tried this->repaint(); */
}
But line this->update(); (or this->repaint();) gave no results. Widget repaint only when I click on it.
So how can I repaint my widget?
The classes that inherit from QAbstractScrollArea as QTreeWidget have viewport() which is the widget that must be updated, so in your case the solution is:
viewport()->update();
If you want to call update from another thread you can use QMetaObject::invokeMethod():
QMetaObject::invokeMethod(viewport(), "update", Qt::QueuedConnection)
This is the solution:
viewport()->update();
I learned one interesting thing. As it turned out, you can update widgets in Qt only from the main thread. My function updated() was called by another thread, so this->update() did not work. However, all slots in Qt are executed just in the main thread, wherever they are called from. In this case, the correct solution would be to wrap this->update() inside the slot. Like this:
TreeWidget::TreeWidget()
{
/* ... */
connect(this, SIGNAL(signal_update()), this, SLOT(slot_update()));
/* ... */
}
void TreeWidget::updated()
{
/* some actions with cells */
emit signal_update();
}
void TreeWidget::slot_update()
{
this->update();
}
Yeah, it's a less beautiful solution than this->viewport()->update() but more correct.
I have a press button (pushButton_RenameTargets) and 3 labels (label_Tar1ex, label_Tar2ex, label_Tar3ex) on my main form with default text values. When I push the button (pushButton_RenameTargets) a dialog is created (renametargets). It has three text edit lines (lineEdit_Target1, lineEdit_Target2,lineEdit_Target3). When I enter names on the three text edit lines and push OK I want the 3 labels on my main form to update.
Better Described:
When the button is pressed:
void MainWindow::on_pushButton_RenameTargets_clicked()
{
RenameTargets renametargets;
renametargets.setModal(true);
renametargets.exec();
}
It creates the dialog window renametargets.
Window has three text edit lines (lineEdit_Target1, lineEdit_Target2,lineEdit_Target3).
When the OK button is pushed I store the text in QString variables.
void RenameTargets::on_buttonBox_TargetRename_accepted()
{
QString Target1NameInput = ui->lineEdit_Target1->text();
QString Target2NameInput = ui->lineEdit_Target2->text();
QString Target3NameInput = ui->lineEdit_Target3->text();
}
Questions:
(1) How can I set the text of QString Target1NameInput (located on second form: renametargets) to label_Tar1ex (located on main form) as I push the OK button on the dialog.
(2) How can I get to display label_Tar1ex (located on main form) to display on a label in the second form -- called label_CurrentName_Tar1ex.
Basically this is a renaming scheme.....
What I would do is declare Target1NameInput and others in your dialog's class instead of your Ok function. That way those variables always "exist" while your dialog exists. If you create them in your Ok function, then they vanish when that function ends, and then you can't get them from your mainWindow anymore.
Move the variable declarations to your dialog's class. (They go in public so other classes can get at em)
class RenameTargets : public QDialog
{
Q_OBJECT
public:
QString Target1NameInput; //Side note, variable naming convention says
QString Target2NameInput; //that variables should start with a lowercase
QString Target3NameInput; //letter, but totally up to you ;)
//Your other class stuff goes here
}
From that point you can set those variables in your dialog when Ok is pressed.
void RenameTargets::on_buttonBox_TargetRename_accepted()
{
Target1NameInput = ui->lineEdit_Target1->text();
Target2NameInput = ui->lineEdit_Target2->text();
Target3NameInput = ui->lineEdit_Target3->text();
}
And lastly, access those variables in your mainWindow.
void MainWindow::on_pushButton_RenameTargets_clicked()
{
RenameTargets renametargets;
renametargets.setModal(true);
if(renametargets.exec() == QDialog::Accepted) //Check if they clicked Ok
{
ui->label_Tar1ex->setText(renametargets.Target1NameInput);
ui->label_Tar2ex->setText(renametargets.Target2NameInput);
ui->label_Tar3ex->setText(renametargets.Target3NameInput);
}
}
As for your second question, sending from mainWindow to dialog, you have 2 options as I see it.
Set your string variables we created in your dialog class before exec().
Pass the text in your dialog constructor.
If option 1, then you simply call renametargets.Target1NameInput = ui->label_Tar1ex->text(); for each variable before you call renametargets.exec(); Then in your dialog's ui setup, you set your lineEdits text to those same variables.
Let me know if you want me to explain option 2 for you. ;)
There's also many other options to send variables between classes, this is just 1 of those ways. I believe the conventional thing to do would be to have get and set functions within your dialog class, but for my own personal projects, I find that overkill. Up to you.
if (editDocumentDialog->exec() == QDialog::Accepted)
{
editDocumentDialog->getDataRecord(theDocRecord);
documents->updateRecord(theDocRecord);
}
Why not using signal / slot?
void MainWindow::on_pushButton_RenameTargets_clicked()
{
RenameTargets renametargets;
connect(&renametargets, SIGNAL(name_inputted), this, SLOT(update_name_fields);
...
}
Then emit the signal in on_buttonBox_TargetRename_accepted, and update label_Tar1ex... in slot function. You may want to create RenameTargets in heap so it isn't destroyed immediately after its OK is clicked.
I have derived from both QGraphicsView and QGraphicsRectItem. I overloaded the contextMenuEvent on both classes to provide popup menus. I want the QGraphicsView context menu when you click on white space the the QGraphicsItem popup menu when you click on an item.
At first implementation, I got the QGraphicsView popup no matter where I clicked. So I modified the contextMenuEvent as follows:
void CustomGraphicsView::contextMenuEvent(QContextMenuEvent* event)
{
if (QGraphicsItem *item = itemAt(event->pos())) {
MyRect* rect = dynamic_cast<MyRect*>(item);
QGraphicsSceneContextMenuEvent* context_event = dynamic_cast<QGraphicsSceneContextMenuEvent*>(event);
if (rect && context_event)
rect->contextMenuEvent(context_event);
}
else {
QMenu menu;
... create the QGraphicsView popup menu
}
}
The dynamic_cast for the QGraphicsSceneContextMenuEvent fails so I never call the contextMenuEvent for the rect. It won't compile if I just try to pass the event to the rect->contextMenu(), so I tried the cast.
What is the right way to do this?
This is a learning project to just create/move/rotate/delete 2D shapes using Qt. If someone wants to look at the whole thing, let me know.
OK, so I figured it out. Just make sure to pass the event through the base class method. Simple! This also works for the mousePressEvent(), mouseMoveEvent(), and mouseReleaseEvent functions.
void CustomGraphicsView::contextMenuEvent(QContextMenuEvent* event)
{
// if the event is on a GGraphicsItem just pass the event along
if (itemAt(event->pos())) {
QGraphicsView::contextMenuEvent(event);
}
else
{
QMenu menu;
... create popup for the CustomGraphicsView
I've tried drawing a rectangle with text inside in a QGraphicsView. I get the text from the currently selected item in a QTreeWidget. The scene seems to sporadically show the text, sometimes it will, sometimes it won't.
void MainWindow::on_treewidget_itemSelectionChanged()
{
drawSectionFromProperties(ui->treewidget->currentItem());
}
void MainWindow::drawSectionFromProperties(QTreeWidgetItem *section)
{
ui->graphicsview->setScene(new QGraphicsScene());
ui->graphicsview->scene()->addRect(0,0,200,300,QPen(QColor(0,0,0)),QBrush(QColor(255,250,129)));
QFont objectTitle;
ui->graphicsview->scene()->addSimpleText(section->text(0),objectTitle);
}
Hmm, looks like you are creating a new scene on each item selection?
This isn't a very nice way to go :)
Better do the following:
Create an 'QGraphicsScene* m_scene;' and 'QGraphicsSimpleTextItem* m_textItem' data members in your MainWindow class private section.
In MainWindow::drawSectionFromProperties() do something like:
MainWindow::MainWindow(QWidget* parent, ...)
: m_scene(0), m_textItem(0)
{
...
}
// leave your on_treewidget_itemSelectionChanged as is
void MainWindow::drawSectionFromProperties(QTreeWidgetItem *section)
{
// setup things only ONE time on the first call
if(!m_scene)
{
m_scene = new QGraphicsScene();
ui->graphicsview->setScene(m_scene);
m_textItem = ui->graphicsview->scene()->addSimpleText(QString());
}
// here only change text of existing item
m_textItem->setText(section->text(0));
}
This way you won't be creating new scene on every item selection. Actually you need ONE scene and ONE item in it, no need to create them over and over and over again and stack one onto another like you currently do.
Cheers :)
I'm using a QMenu as context menu. This menu is filled with QActions. One of these QActions is checkable, and I'd like to be able to check/uncheck it without closing the context menu (and having to re-open it again to choose the option that I want).
I've tried disconnecting the signals emitted by the checkable QAction with no luck.
Any ideas? Thanks.
Use a QWidgetAction and QCheckBox for a "checkable action" which doesn't cause the menu to close.
QCheckBox *checkBox = new QCheckBox(menu);
QWidgetAction *checkableAction = new QWidgetAction(menu);
checkableAction->setDefaultWidget(checkBox);
menu->addAction(checkableAction);
In some styles, this won't appear exactly the same as a checkable action. For example, for the Plastique style, the check box needs to be indented a bit.
There doesn't seem to be any elegant way to prevent the menu from closing. However, the menu will only close if the action can actually trigger, i.e. it is enabled. So, the most elegant solution I found is to trick the menu by shortly disabling the action at the moment when it would be triggered.
Subclass QMenu
Reimplement relevant event handlers (like mouseReleaseEvent())
In the event handler, disable the action, then call base class' implementation, then enable the action again, and trigger it manually
This is an example of reimplemented mouseReleaseEvent():
void mouseReleaseEvent(QMouseEvent *e)
{
QAction *action = activeAction();
if (action && action->isEnabled()) {
action->setEnabled(false);
QMenu::mouseReleaseEvent(e);
action->setEnabled(true);
action->trigger();
}
else
QMenu::mouseReleaseEvent(e);
}
To make the solution perfect, similar should be done in all event handlers that may trigger the action, like keyPressEvent(), etc...
The trouble is that it is not always easy to know whether your reimplementation should actually trigger the action, or even which action should be triggered. The most difficult is probably action triggering by mnemonics: you would need to reimplement the complex algorithm in QMenu::keyPressEvent() yourself.
This is my solution:
// this menu don't hide, if action in actions_with_showed_menu is chosen.
class showed_menu : public QMenu
{
Q_OBJECT
public:
showed_menu (QWidget *parent = 0) : QMenu (parent) { is_ignore_hide = false; }
showed_menu (const QString &title, QWidget *parent = 0) : QMenu (title, parent) { is_ignore_hide = false; }
void add_action_with_showed_menu (const QAction *action) { actions_with_showed_menu.insert (action); }
virtual void setVisible (bool visible)
{
if (is_ignore_hide)
{
is_ignore_hide = false;
return;
}
QMenu::setVisible (visible);
}
virtual void mouseReleaseEvent (QMouseEvent *e)
{
const QAction *action = actionAt (e->pos ());
if (action)
if (actions_with_showed_menu.contains (action))
is_ignore_hide = true;
QMenu::mouseReleaseEvent (e);
}
private:
// clicking on this actions don't close menu
QSet <const QAction *> actions_with_showed_menu;
bool is_ignore_hide;
};
showed_menu *menu = new showed_menu ();
QAction *action = menu->addAction (new QAction (menu));
menu->add_action_with_showed_menu (action);
Here are couple ideas I've had... Not sure at all they will work tho ;)
1) Try to catch the Event by using the QMenu's method aboutToHide(); Maybe you can "Cancel" the hide process ?
2) Maybe you could consider using an EventFilter ?
Try to have a look at : http://qt.nokia.com/doc/4.6/qobject.html#installEventFilter
3) Otherwise you could reimplement QMenu to add your own behavior, but it seems a lot of work to me...
Hope this helps a bit !
(I started with Andy's answer, so thank you Andy!)
1) aboutToHide() works, by re-popping the menu at a cached position, BUT it can also enter an infinite loop. Testing if the mouse is clicked outside the menu to ignore re-opening should do the trick.
2) I tried an event filter but it blocks the actual click to the menu item.
3) Use both.
Here is a dirty pattern to prove that it works. This keeps the menu open when the user holds down CTRL when clicking:
# in __init__ ...
self.options_button.installEventFilter(self)
self.options_menu.installEventFilter(self)
self.options_menu.aboutToHide.connect(self.onAboutToHideOptionsMenu)
self.__options_menu_pos_cache = None
self.__options_menu_open = False
def onAboutToHideOptionsMenu(self):
if self.__options_menu_open: # Option + avoid an infinite loop
self.__options_menu_open = False # Turn it off to "reset"
self.options_menu.popup(self.__options_menu_pos_cache)
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.MouseButtonRelease:
if obj is self.options_menu:
if event.modifiers() == QtCore.Qt.ControlModifier:
self.__options_menu_open = True
return False
self.__options_menu_pos_cache = event.globalPos()
self.options_menu.popup(event.globalPos())
return True
return False
I say it is dirty because the widget here is acting as an event filter for both the button that opens the menu as well as the menu itself. Using explicit event filter classes would be easy enough to add and it would make things a little easier to follow.
The bools could probably be replaced with a check to see if the mouse is over the menu, and if not, don't pop it open. However, the CTRL key still has to be factored in for my use case, so it probably isn't far off a nice solution as it is.
When the user holds down CTRL and clicks on the menu, it flips a switch so the menu opens itself back up when it tried to close. The position is cached so it opens at the same position. There is a quick flicker, but it feels OK since the user knows they are holding a key down to make this work.
At the end of the day (literally) I already had the whole menu doing the right thing. I just wanted to add this functionality and I definitely didn't want to change to using a widget just for this. For this reason, I am keeping even this dirty patch for now.
Subclass QMenu and override setVisible. You can utilize activeAction() to know if an action was selected and the visible arg to see if the QMenu is trying to close, then you can override and call QMenu::setVisible(...) with the value you want.
class ComponentMenu : public QMenu
{
public:
using QMenu::QMenu;
void setVisible(bool visible) override
{
// Don't hide the menu when holding Shift down
if (!visible && activeAction())
if (QApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier))
return;
QMenu::setVisible(visible);
}
};
Connect the QMenu.show to the action trigger. I know this is code for Qt5 (and in Python), but the principle should be back compatible.
from PyQt5 import QtWidgets
class CheckableMenu(QtWidgets.QMenuBar):
def __init__(self,parent=None):
super().__init__(parent)
self.menuObj=QtWidgets.QMenu("View")
self.addMenu(self.menuObj)
for i in ['Both','Even','Odd']: #my checkable items
t=QtWidgets.QAction(i,self.menuObj,checkable=True)
t.triggered.connect(self.menuObj.show)
self.menuObj.addAction(t)
Starting from baysmith solution, the checkbox didn´t work as I was expecting, because I was connecting to the action triggered(), rather than connecting to the checkbox toggled(bool). I´m using the code to open a menu with several checkboxes when I press a button :
QMenu menu;
QCheckBox *checkBox = new QCheckBox("Show Grass", &menu);
checkBox->setChecked(m_showGrass);
QWidgetAction *action = new QWidgetAction(&menu);
action->setDefaultWidget(checkBox);
menu.addAction(action);
//connect(action, SIGNAL(triggered()), this, SLOT(ToggleShowHardscape_Grass()));
connect(checkBox, SIGNAL(toggled(bool)), this, SLOT(ToggleShowHardscape_Grass()));
menu.exec(QCursor::pos() + QPoint(-300, 20));
For my use case, this works like a charm
I have been struggling with this for half a day.
There was many accepted answers on the net suggesting overriding setVisible function of the QMenu which did not work for me at all.
I found a solution based on this post (The last reply by the OP)
my C++ implementation for this matter is as follows:
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::MouseButtonRelease)
{
auto action = static_cast<QMenu*>(watched)->activeAction();
if (action && action->isCheckable())
{
action->trigger();
return true;
}
}
return QObject::eventFilter(watched, event);
}