Controlling a popup QWidget attached to a QToolButton - qt

I want to get a Popup Widget to be shown when clicking on a QToolbutton.
This can be done by adding an action to the button itself.
The popup will contain a three buttons (update, create and cancel) and a text input field.
I have some sample code with only one button I have shared as a Github repository.
The most relevant part of the code is:
auto button = new QToolButton(this);
button->setText(" AA ");
auto popup = new Popup(button, this);
auto popupAction = new QWidgetAction(this);
popupAction->setDefaultWidget(popup);
button->setPopupMode(QToolButton::InstantPopup);
button->addAction(popupAction);
The result is as follow:
I have two issues I cannot solve:
Getting the popup widget to right align to the button.
Getting the popup widget to close when one of the buttons inside of it are clicked.
Right align the popup
There is already a similar question: Set position (to right) of Qt QPushButton popup menu.
I can add the suggested code:
void Popup::showEvent(QShowEvent*)
{
QPoint p = this->pos();
QRect geo = clickedButton->geometry();
this->move(p.x()+geo.width()-this->geometry().width(), p.y());
}
But only the content of the popup gets right aligned to the button, not the popup itself:
Closing the popup
If I click anywhere (but a widget) in the Popup it closes. I'm somehow fine with this.
But if I cannot manage to get a click on the button to close the popup.
I've tried to call the close() function but it only clears the content of the popup without closing it.
Can I get the button to trigger a signal and then close the popup?
I ask both questions as the same time, since they look very similar: both times it's the content and not the popup that is affected.

auto popup = new Popup(button, this);
auto popupAction = new QWidgetAction(this);
popupAction->setDefaultWidget(popup);
button->setPopupMode(QToolButton::InstantPopup);
button->addAction(popupAction);
Your Popup widget is not the actual popup, but just a widget inside the real popup.
So what you're moving is the widget inside the real popup and not the popup itself.
The solution in the question you linked to uses QMenu and it works with QMenu.
In your code replace
connect(button, &QToolButton::clicked, this, &MainWindow::showPopup);
auto updateButton = new QPushButton("Update");
auto popupAction = new QWidgetAction(this);
popupAction->setDefaultWidget(updateButton);
button->setPopupMode(QToolButton::InstantPopup);
button->addAction(popupAction);
with
button->setPopupMode(QToolButton::InstantPopup);
auto menu = new Popup(button, this);
auto action = new QAction("Test");
menu->addAction(action);
button->setMenu(menu);
Change your Popup class to extend/inherit QMenu, uncomment the showEvent method and remove everything from the constructor.
EDIT
You can set a layout to the qmenu and add widgets to it.
auto menuLayout = new QGridLayout();
auto menuBtn1 = new QPushButton("Btn1");
auto menuBtn2 = new QPushButton("Btn2");
auto menuBtn3 = new QPushButton("Btn3");
menuLayout->addWidget(menuBtn1, 0, 0);
menuLayout->addWidget(menuBtn2, 0, 1);
menuLayout->addWidget(menuBtn3, 1, 0);
button->setPopupMode(QToolButton::InstantPopup);
auto menu = new Popup(button, this);
menu->setLayout(menuLayout);

When you use a layout it has margins that prevent alignment.
The QMenu of the Popup can be accessed through kinship but this must be accessed when the button is pressed as this is created when it is first shown.
popup.h
#ifndef POPUP_H
#define POPUP_H
#include <QWidget>
class QToolButton;
class Popup : public QWidget
{
Q_OBJECT
public:
explicit Popup(QWidget* parent=nullptr);
Q_SIGNALS:
void clicked();
};
#endif
popup.cpp
#include "popup.h"
#include<QWidget>
#include<QVBoxLayout>
#include<QPushButton>
Popup::Popup(QWidget* parent)
: QWidget(parent)
{
auto layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addStretch();
auto updateButton = new QPushButton("Update");
layout->addWidget(updateButton);
connect(updateButton, &QPushButton::clicked, this, &Popup::clicked);
}
mainwindow.h
MainWindow::MainWindow()
{
auto widget = new QWidget;
setCentralWidget(widget);
auto layout = new QHBoxLayout(widget);
layout->addStretch();
auto button = new QToolButton;
button->setText(" AA ");
layout->addWidget(button);
auto popup = new Popup;
auto popupAction = new QWidgetAction(this);
popupAction->setDefaultWidget(popup);
button->setPopupMode(QToolButton::InstantPopup);
button->addAction(popupAction);
connect(popup, &Popup::clicked, [popup](){
if(QWidget *p = popup->parentWidget())
p->close();
});
}

Related

QVBoxLayout push to the top with spacer

I would like to add push buttons to the layout. The newest item would be on the top of the layout.
I also would like position the buttons to the top, thus I am using QSpacerItem.
Here is what I have tried so far.
Constructor:
//frame is a QFrame
lVertical = new QVBoxLayout(frame); //private variable
lVertical->setMargin(0);
lVertical->setSpacing(0);
auto verticalSpacer = new QSpacerItem(10, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
lVertical->addItem(verticalSpacer);
connect(b, &QPushButton::clicked, this, &MainWindow::addToLayout);
Function:
void MainWindow::addToLayout() {
QPushButton* button = new QPushButton(frameSlider);
button->setText(QString::number(i));
++i; //private variable
layoutVertical->addWidget(button);
}
Currently I add like this:
But I would like to add like this:
The problem is that you have placed a spacer at the beginning so it will stretch all the widgets down.
One possible solution is to add a stretch and then insert an element before, You should not use QSpacerItem:
// constructor
layoutVertical = new QVBoxLayout(frame);
layoutVertical->setMargin(0);
layoutVertical->setSpacing(0);
layoutVertical->addStretch();
void MainWindow::addToLayout() {
QPushButton* button = new QPushButton();
layoutVertical->insertWidget(0, button);
}

Can we shift widget between the cells of a table widget

I've created a a widget in which I've placed two buttons using a layout and placed it inside a table widget's cell. The thing is that I'm changing the size of the table and I want to shift the placement of the widget without deleting them and re-initializing them from the beginning because I already assigned them actions on click (I think that the application would crash in this situation)
Code:
btn = new QPushButton[horzHeaders.size()];
btn[j].setParent(ui->tableWidget);
btn[j].setIcon(QIcon("./save.png"));
btn[j].setVisible(true);
btn_Load = new QPushButton[horzHeaders.size()];
btn_Load[j].setParent(ui->tableWidget);
btn_Load[j].setIcon(QIcon("./upload.png"));
btn_Load[j].setVisible(true);
lay = new QHBoxLayout[horzHeaders.size()];
lay[j].addWidget(&btn[j]);
lay[j].addWidget(&btn_Load[j]);
QWidget *w = new QWidget[horzHeaders.size()];
w[j].setLayout(&lay[j]);
ui->tableWidget->setCellWidget(j,vertHeaders.size() - 1, &w[j]);
You can add and remove widgets from a layout anytime you want
QWidget *widget = new QWidget();
QPushButton *button = new QPushButton();
QHBoxLayout *Hbox = new QHBoxLayout();
QVBoxLayout *Vbox= new QVBoxLayout();
Hbox->addWidget(button);
// use it till the window is resized
//and then check with an if-statement if the window is resized or not
Hbox->removeWidget(button); // remove from the button from layout
layout()->removeAt(widget); //remove the widget's current layout
Vbox->addWidget(button); // add button widget to vertical layout
widget->setLayout(Vbox); // Give it a new layout
widget->setLayout(Vbox);
If this didn't answer your question then ask in the comments.

Qt - QStackedLayout setCurrentIndex displays blank window instead of widget

I just started learning Qt a couple days ago to make a game, and I'm trying to figure out how to make layouts work.
What I want is a Window using QStackedLayout with 2 widgets inside: StartScreen and GameScreen. StartScreen is shown on top on program run. A button in StartScren is connected to a function inside Window, and Window will call setCurrentIndex to change the widget on top to GameScreen.
What happens right now is that when I click on the button, the view changes to a blank window. I've tried hardcoding to setCurrentIndex(0), which does nothing, setCurrentIndex(1), which is what GameScreen should be and displays the same blank window, and setCurrentIndex(2), which is out of index bounds but still does nothing. So the connection is going through, but I don't understand why a blank window will show up instead of the button I have on GameScreen.
If someone can explain to me what concept I missed and how to fix it, I'd greatly appreciate it. Thanks!
Here is window.cpp:
Window::Window(QWidget *parent) : QWidget(parent)
{
resize(640, 480);
layout = new QStackedLayout;
createStartScreen();
createGameScreen();
setLayout(layout);
show();
};
void Window::createStartScreen(){
start = new StartScreen();
layout->addWidget(start);
start->setWindow(this);
}
void Window::playGame(){
layout->setCurrentIndex(layout->indexOf(game));
}
void Window::createGameScreen(){
game = new GameScreen();
layout->addWidget(game);
}
startscreen.cpp:
StartScreen::StartScreen(QWidget *parent) : QWidget(parent)
{
newGameButton = new QPushButton("New Game", this);
newGameButton->setGeometry(QRect(QPoint(260, 300), QSize(120,40)));
quitButton = new QPushButton("Quit", this);
quitButton->setGeometry(QRect(QPoint(260, 360), QSize(120,40)));
connect(quitButton, SIGNAL(clicked()), QApplication::instance(), SLOT(quit()));
};
void StartScreen::setWindow(Window *w){
connect(newGameButton, SIGNAL(clicked()), w, SLOT(playGame()));
}
gamescreen.cpp:
GameScreen::GameScreen(QWidget *parent) : QWidget(parent)
{
button = new QPushButton("Hi");
button->setGeometry(QRect(QPoint(260, 260), QSize(120,40)));
};
That's because you don't call show on button. But you should rather use a layout to handle it.
e.g.:
GameScreen::GameScreen(QWidget *parent)
: QWidget(parent)
{
button = new QPushButton(tr("Hi"));
QHBoxLayout *layout = new QHBoxLayout(this);
layout->addWidget(button);
};
button will have a size adapted to its text and will be cleaned positioned in GameScreen.
On a side note, you should rather add a signal to StartScreen to request a new game and do the connection in Window. That way you won't have a tight coupling between StartScreen and Window.

QDialog and scroll areas: how to merge them?

From the ImageViewer example:
ImageViewer::ImageViewer()
{
imageLabel = new QLabel;
imageLabel->setBackgroundRole(QPalette::Base);
imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
imageLabel->setScaledContents(true);
scrollArea = new QScrollArea;
scrollArea->setBackgroundRole(QPalette::Dark);
scrollArea->setWidget(imageLabel);
setCentralWidget(scrollArea);
resize(500, 400);
}
I need the scroll area to appear not in the central widget, but in a dialog inside the central widget.
I tried with:
ImageViewer::ImageViewer()
{
QImage image(fileName);
plotImg = new QLabel;
plotImg->setBackgroundRole(QPalette::Base);
plotImg->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
plotImg->setScaledContents(true);
plotImg->setPixmap(QPixmap::fromImage(image));
scrollArea = new QScrollArea(this);
scrollArea->setWidget(plotImg);
scrollArea->setBackgroundRole(QPalette::Dark);
printAct->setEnabled(true);
fitToWindowAct->setEnabled(true);
if(!fitToWindowAct->isChecked())
plotImg->adjustSize();
return true;
}
From this code, I get the dialog inside the central widget. This dialog, however, does not contain the image itself, but the scroll area which contains the image.
I would like the dialog and the scroll area to be "the same thing"...
Easy. Don't use a dialog, simply have ImageViewer inherit directly from QScrollArea.

Qt: Architectural advice needed regarding QAbstractScrollArea

I'd like to design a scrollable "controls container" widget. Meaning, a scrollable view that'll be able to contain live controls (any QWidget derivative). By "live controls" i mean, if a animated QWidget derived is placed in it, i'd like to see the animation, as i scroll up and down, while the sub-control moves up and down.
Would basing such a widget on "QAbstractScrollArea" be the right way to approach it? i'd simply add controls as children? positioning them in a column? will that be enough?
EDIT:
This is the constructor code from my QAbstractScrollArea derived class. Why don't I ever see a scrollbar that can scroll the controls? (not all are visible on the same page based on the height I gave my control)
// add controls
QPushButton *a = new QPushButton(QString("a"), this);
a->setGeometry(QRect(10,10,100,30));
QPushButton *b = new QPushButton(QString("b"), this);
b->setGeometry(QRect(10,40,100,30));
QPushButton *c = new QPushButton(QString("c"), this);
c->setGeometry(QRect(10,70,100,30));
QPushButton *d = new QPushButton(QString("d"), this);
d->setGeometry(QRect(10,100,100,30));
QPushButton *e = new QPushButton(QString("e"), this);
e->setGeometry(QRect(10,130,100,30));
QPushButton *f = new QPushButton(QString("f"), this);
f->setGeometry(QRect(10,160,100,30));
QPushButton *g = new QPushButton(QString("g"), this);
g->setGeometry(QRect(10,190,100,30));
QPushButton *h = new QPushButton(QString("h"), this);
h->setGeometry(QRect(10,220,100,30));
this->addScrollBarWidget(new QScrollBar(this), Qt::AlignRight);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
Basically, that is enough. Use the concrete QScrollArea class and a generic container widget, then position your controls as children of the container.
QScrollArea scrollArea;
QWidget container;
// Create controls and add them to container.
scrollArea.setWidget( &container );

Resources