QMenu not execing at correct position first time - c++

I have this very strange issue regarding a QMenu and its position when execing.
Here is the code for my subclassed QMenu:
DockItemContextMenu::DockItemContextMenu(QWidget *parent) : QMenu(parent){
style = qApp->style();
QPointer<QAction> restoreAction = new QAction(QIcon(style->standardIcon(QStyle::SP_TitleBarMaxButton)), "Restore", this);
QPointer<QAction> minimizeAction = new QAction(style->standardIcon(QStyle::SP_TitleBarMinButton), "Minimize", this);
QPointer<QAction> maximizeAction = new QAction(style->standardIcon(QStyle::SP_TitleBarMaxButton), "Maximize", this);
QPointer<QAction> stayOnTopAction = new QAction("Stay On Top", this);
stayOnTopAction->setCheckable(true);
QPointer<QAction> closeAction = new QAction(style->standardIcon(QStyle::SP_TitleBarCloseButton), "Close", this);
this->addActions({restoreAction, minimizeAction, maximizeAction, stayOnTopAction, closeAction});
connect(restoreAction, &QAction::triggered, parent, [this](){ emit restoreTriggered();}, Qt::QueuedConnection);
connect(minimizeAction, &QAction::triggered, parent, [this](){ emit minimizeTriggered();}, Qt::QueuedConnection);
connect(maximizeAction, &QAction::triggered, parent, [this](){ emit maximizeTriggered();}, Qt::QueuedConnection);
connect(stayOnTopAction, &QAction::triggered, parent, [this](){ emit stayOnTopTriggered();}, Qt::QueuedConnection);
connect(closeAction, &QAction::triggered, parent, [this](){ emit closeTriggered();}, Qt::QueuedConnection);
}
Okay, so essentially I have another widget who holds an instance of this DockItemContextMenu as a field. In this owning class, called Titlebar, I made it such that doing a right click will emit the customContextMenuRequested(QPoint) signal.
TitleBar::TitleBar(QString title, QWidget *parent){
...
this->setContextMenuPolicy(Qt::CustomContextMenu);
contextMenu = new DockItemContextMenu(this);
connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)), Qt::QueuedConnection);
...
}
After this, this widget is essentially inserted into a QGraphicsScene and is converted implicitly into a QGraphicsItem. When I do the FIRST right click event on my Titlebar it will not exec at the correct screen position if I dragged the MainWindow of the entire QApplication anywhere other than its starting position on screen. In addition to being in a QGraphicsScene, this scene itself is always stored in a QSplitter. Now I would understand if this always had some sort of issue, but it turns out, every time I call the slot for that signal, ONLY the first time will it exec in the incorrect position in the QGraphicsScene. No matter how I manipulate the size of the Titlebar widget itself, move commands or maximize commands to the MainWindow, or even edit the splitter size for the QGraphicsView that affects the size of the QGraphicsScene, it will always be in the correct position afterwards. here is the function for execing:
void TitleBar::showContextMenu(QPoint point){
qDebug() << point;
contextMenu->exec(point);
emit _parent->focusChangedIn();
}
I printed the point at which it is calling the exec. The strangest part is that both times I right click in the same location, it will print the SAME value for the slot's positional parameter both the first exec and second exec, but be in the correct location every time other than the first. Did I forget to set some other flag when I added the context menu to the Titlebar class? Does it have anything to do with setting the QMenu's parent to the Titlebar? I'm just dumbfounded how the same QPoint could exec at two different screen locations given the same value. Does anybody have a clue what may or may not be happening on the first call to the Titlebar's slot for execing the QMenu?
EDIT: The issue stemmed from doing this line of code in the Titlebar constructor:
contextMenu = new DockItemContextMenu(this);
Changing it to:
contextMenu = new DockItemContextMenu;
fixed the issue. Does anyone know why, or is this possibly a bug? I rather not accept this as an answer because it does not explain why it happened in the first place.
Here is a minimal example with the same effect.
MainWindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QWidget>
#include <QGraphicsView>
#include <QSplitter>
#include <QHBoxLayout>
#include <QGraphicsScene>
#include <QPointer>
#include <QTreeWidget>
#include "titlebar.h"
class MainWindow : public QMainWindow{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
};
#endif // MAINWINDOW_H
MainWindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){
QPointer<QWidget> widgetArea = new QWidget;
QPointer<QHBoxLayout> hLayout = new QHBoxLayout;
widgetArea->setLayout(hLayout);
QPointer<QSplitter> splitter = new QSplitter;
hLayout->addWidget(splitter);
QPointer<QTreeView> tree = new QTreeView;
splitter->addWidget(tree);
QPointer<QGraphicsView> view = new QGraphicsView;
splitter->addWidget(view);
splitter->setStretchFactor(0, 1);
splitter->setStretchFactor(1, 4);
QPointer<QGraphicsScene> scene = new QGraphicsScene;
view->setScene(scene);
QPointer<Titlebar> blue = new Titlebar;
blue->setObjectName("blue");
blue->setStyleSheet(QString("#blue{background-color: rgb(0,0,255)}"));
blue->resize(250,250);
scene->addWidget(blue);
this->setCentralWidget(widgetArea);
this->resize(1000,750);
}
MainWindow::~MainWindow(){
}
Titlebar.h:
#ifndef TITLEBAR_H
#define TITLEBAR_H
#include <QMenu>
#include <QWidget>
#include <QPointer>
#include <QDebug>
#include <QMouseEvent>
class Titlebar : public QWidget{
Q_OBJECT
public:
explicit Titlebar(QWidget *parent = nullptr);
QPointer<QMenu> menu;
QPoint currentPos;
protected slots:
void mousePressEvent(QMouseEvent* event);
void mouseMoveEvent(QMouseEvent* event);
void showContextMenu(QPoint point);
};
#endif // TITLEBAR_H
Titlebar.cpp:
#include "titlebar.h"
Titlebar::Titlebar(QWidget *parent) : QWidget(parent){
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)), Qt::QueuedConnection);
menu = new QMenu(this);
menu->addAction("Test");
}
void Titlebar::showContextMenu(QPoint point){
qDebug() << point;
menu->exec(mapToGlobal(point));
}
void Titlebar::mouseMoveEvent(QMouseEvent *event){
if (event->buttons() && Qt::LeftButton){
QPoint diff = event->pos() - currentPos;
move(pos() + diff);
}
}
void Titlebar::mousePressEvent(QMouseEvent * event){
currentPos = event->pos();
}
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
So this runs and reproduces the error accordingly. If you change the line in Titlebar.cpp from
menu = new QMenu(this);
to:
menu = new QMenu;
Then it works correctly. ONLY the first right click to open the context menu will spawn in the incorrect location on screen. All subsequent right clicks will now follow either the widget/window/splitter in any combination. I don't get it, can someone tell me if this is actually a bug or not.

You need to add one line of code because your using a QGraphicsProxyWidget which is part of a QGraphicsScene. The scene is represented by a QGraphicsView which inherits QAbstractScrollArea. This causes the context menu to be shown via the viewport and not the widget itself. Therefore adding this one line of code will override the title bar to not be embedded in the scene when it's parent was already embedded in the scene. Effectively making it reference the widget again and not the viewport.
In the MainWindow.cpp right after line 26 add
blue->setWindowFlags(Qt::BypassGraphicsProxyWidget);

Related

QPushButton not executing connected function on click

I have a class called UserInterface. In this class there are different functions. build_start_screen() adds all the widgets (labels, buttons,...) for the initial startscreen. build_option_a_screen() removes everything from the startscreen and adds all the widgets needed for the screen when the users click on the button for option A, and so on. The class is stripped down for the sake of this question.
Now I have declared a button in build_start_screen() and connected it to a simple MessageBox.exec() so it should pop-up on click.
However, nothing happens when the button gets clicked.
What am I doing wrong? Has it something to do with the lifetime of variables expiring after the function finishes?
#include <QApplication>
#include <QPushButton>
#include <QAbstractButton>
#include <QLabel>
#include <QFont>
#include <QVBoxLayout>
#include <QMessageBox>
//Class handling all the UI in this Application
class UserInterface {
public:
//Build the initial UI the user sees
void build_start_screen(QWidget& window) {
//Make new QVBoxLayout for this startscreen UI
this->layout = new QVBoxLayout(&window);
//Test messagebox
QMessageBox msgBox;
msgBox.setText("Button test.");
//Button to go to Option A-screen
QPushButton* showMsgBox = new QPushButton("Show pop-up");
QAbstractButton::connect(showMsgBox, SIGNAL (clicked()), &window, SLOT (msgBox.exec()));
//Add labels and button to QVBoxLayout
layout->addWidget(showMsgBox);
}
private:
//Properties
QVBoxLayout* layout;
};
int main(int argc, char **argv) {
QApplication app (argc, argv);
//Initialize Window
QWidget Window;
Window.resize(400, 250);
//Create new UserInterface object
//This will allow us to create different user-interfaces
//depending on the function we call
UserInterface* ui = new UserInterface();
ui->build_start_screen(Window);
Window.show();
return app.exec();
}
And what if I'd like to do the same, but instead of calling a messageBox I'd like to call another function?
#include <QApplication>
#include <QPushButton>
#include <QAbstractButton>
#include <QLabel>
#include <QFont>
#include <QVBoxLayout>
#include <QMessageBox>
//Class handling all the UI in this Application
class UserInterface {
public:
//Build the initial UI the user sees
void build_start_screen(QWidget& window) {
//Make new QVBoxLayout for this startscreen UI
this->layout = new QVBoxLayout(&window);
//Test messagebox
QMessageBox msgBox;
msgBox.setText("Button test.");
//Button to go to Option A-screen
QPushButton* showMsgBox = new QPushButton("Show pop-up");
QAbstractButton::connect(showMsgBox, SIGNAL (clicked()), &window, SLOT (build_option_a_screen()));
//Add labels and button to QVBoxLayout
layout->addWidget(showMsgBox);
}
void build_option_a_screen(QWidget& window) {
//Do stuff here with window
//e.g
window.resize(500, 500);
}
private:
//Properties
QVBoxLayout* layout;
};
int main(int argc, char **argv) {
QApplication app (argc, argv);
//Initialize Window
QWidget Window;
Window.resize(400, 250);
//Create new UserInterface object
//This will allow us to create different user-interfaces
//depending on the function we call
UserInterface* ui = new UserInterface();
ui->build_start_screen(Window);
Window.show();
return app.exec();
}
Your code has 2 problems:
The window "object" does not have slot "msgBox.exec()" as pointed out by the error:
QObject::connect: No such slot QWidget::msgBox.exec() in ../main.cpp:23
Correcting the above, the solution would be:
QObject::connect(showMsgBox, &QPushButton::clicked, &msgBox, &QMessageBox::exec);
but now the problem is that "msgBox" is a local variable that will be destroyed and cannot be displayed.
So the solution is to make msgBox a member of the class or a pointer (in the case of the pointer you must manage the dynamic memory to avoid memory leaks):
//Class handling all the UI in this Application
class UserInterface {
public:
//Build the initial UI the user sees
void build_start_screen(QWidget& window) {
//Make new QVBoxLayout for this startscreen UI
this->layout = new QVBoxLayout(&window);
msgBox.setText("Button test.");
//Button to go to Option A-screen
QPushButton* showMsgBox = new QPushButton("Show pop-up");
QObject::connect(showMsgBox, &QPushButton::clicked, &msgBox, &QMessageBox::exec);
//Add labels and button to QVBoxLayout
layout->addWidget(showMsgBox);
}
private:
//Properties
QVBoxLayout* layout;
QMessageBox msgBox;
};
Plus:
It is recommended not to use the old connection syntax as it has limitations and hides the problems.
It is recommended not to use the old connection syntax as it has limitations and hides the problems.
If you want to connect to a method of some kind that is not a QObject (for example X as you want the OP) then the solution is to use a lambda method:
//Class handling all the UI in this Application
class UserInterface {
public:
//Build the initial UI the user sees
void build_start_screen(QWidget& window) {
//Make new QVBoxLayout for this startscreen UI
this->layout = new QVBoxLayout(&window);
//Button to go to Option A-screen
QPushButton* showMsgBox = new QPushButton("Show pop-up");
QObject::connect(showMsgBox, &QPushButton::clicked, [this, &window](){
build_option_a_screen(window);
});
//Add labels and button to QVBoxLayout
layout->addWidget(showMsgBox);
}
void build_option_a_screen(QWidget& window) {
//Do stuff here with window
//e.g
window.resize(500, 500);
}
private:
//Properties
QVBoxLayout* layout;
};

How to make invisible button on widget with background image?

I want to make a simple application with invisible button.
I set background image for my widget by UI property styleSheet and Resources -
border-image:url(:/image.jpg).
I always get something like this
and then I try to add button on it
I was trying with
ui->pushButton->setStyleSheet("QPushButton{background: transparent;}");
ui->pushButton->setStyleSheet("background-color: rgba(255, 255, 255, 0);");
and it works with buttons on default background, but not in my case.
Every button that I add takes default parent background image. I dont want to see any hints of a button, but when I click on an area to be able to perform some functionality.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->centralWidget->setStyleSheet("background-image:url(:image.jpg)");
ui->pushButton->setStyleSheet("QPushButton{border:none;}");
}
Code an above makes button flat, but it duplicate background image from parent widget anyway.
Have you any idea how to resolve it?
Cause
A common misconception is that when a stylesheet without a selector is applied to an element, then it is used only for that element. In fact all element's children are styled as well. Thus a selector should be used to achieve the expected result.
Solution
I would suggest you to change this line in your code
ui->centralWidget->setStyleSheet("background-image:url(:image.jpg)");
to
ui->centralWidget->setStyleSheet(".QWidget { background-image:url(:image.jpg) }");
Important: Note the dot before QWidget. It means style the QWidget, but exclude the subclasses. This is necessary because QPushButton is a subclass of QWidget and otherwise would be affected as well.
Then you can set the pushButton's backgroung color to transparent as you do with
ui->pushButton->setStyleSheet("QPushButton{background: transparent;}");
Example
Here is a simple example I have prepared for you in order to demonstrate the proposed solution (requires cat.png in the resource file under pix/images):
#include <QMainWindow>
#include <QWidget>
#include <QPushButton>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr) :
QMainWindow(parent) {
auto *widget = new QWidget(this);
auto *button = new QPushButton(widget);
widget->setStyleSheet(".QWidget {"
" background-image:url(':/pix/images/cat.png');"
" background-repeat: no-repeat;"
"}");
button->setStyleSheet(".QPushButton {"
" background-color: transparent"
"}");
button->move(100, 100);
button->resize(100, 100);
connect(button, &QPushButton::clicked, [](){
qDebug("clicked");
});
setCentralWidget(widget);
resize(600, 480);
}
};
Result
The given example produces a window with a background and a 100x100px invisible clickable area positioned at (100, 100):
I think it's better to answer here than in comments.
You just have to set the following stylesheet for your QPushButton to make it invisible:
QPushButton
{
border: none;
}
I've made the test and it worked well.
For the tests, I have set the wrapping widget's background-image property. I also did another test with the background-color property instead. It worked in both cases (whether the background is a plain color or a picture/photo).
I hope it helps.
EDIT:
I have written a widget that performs what you want. And I also provided a windows in order to make the below example minimal and complete so that you can reproduce it.
I have tested it and it worked well.
test.h:
#ifndef TEST_H
#define TEST_H
#include <QMainWindow>
#include <QPushButton>
class WidgetWithHiddenButton : public QWidget
{
Q_OBJECT
protected:
QPushButton * invisible_button;
public:
WidgetWithHiddenButton(QWidget * parent = nullptr);
QPushButton * getButton();
protected:
void paintEvent(QPaintEvent *) override;
};
class TestWindow final : public QMainWindow
{
Q_OBJECT
private:
WidgetWithHiddenButton * widget;
public:
TestWindow();
};
#endif // TEST_H
test.cpp:
#include "test.h"
#include <QApplication>
#include <QStyleOption>
#include <QPainter>
#include <QVBoxLayout>
WidgetWithHiddenButton::WidgetWithHiddenButton(QWidget * parent) : QWidget(parent)
{
// build your widget as you want.
invisible_button = new QPushButton("Here is a button", this);
QVBoxLayout * lay = new QVBoxLayout;
QHBoxLayout * inner_lay = new QHBoxLayout;
inner_lay->addStretch();
inner_lay->addWidget(invisible_button);
inner_lay->addStretch();
lay->addLayout(inner_lay);
this->setLayout(lay);
this->setStyleSheet("WidgetWithHiddenButton {background-image: url(path_to_image/image.jpg);}");
invisible_button->setStyleSheet("QPushButton {border: none;}");
}
QPushButton * WidgetWithHiddenButton::getButton()
{
return invisible_button;
}
void WidgetWithHiddenButton::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
TestWindow::TestWindow()
{
resize(500, 300);
widget = new WidgetWithHiddenButton;
this->setCentralWidget(widget);
connect(widget->getButton(), &QPushButton::clicked, qApp, &QApplication::quit);
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
TestWindow tw;
tw.show();
return app.exec();
}
Feel free to adapt it (especially by changing the class name because WidgetWithHiddenButton is very ugly :) ).
Notes:
I have written a text in the button in order to make it visible (for tests purposes) but you can remove it if you want the button completely invisible.
I connected the QPushButton::clicked() signal to the QApplication::quit() slot in order to perform an action when we click on the area of the button.
I redefined the paintEvent() method because it is needed when using Q_OBJECT macro alongside stylesheets over a custom QWidget as the documentation mentioned.
Feel free to modify the way I build the widget in the constructor (layouts, sizes, ...) to make it fit your requirements.

QGraphicsView::NoViewportUpdate doesn't work

I have a very simple window with a QGraphicsView, a QGraphicsScene inside, and a simple QPushButton. When user clicks button, a line should be added to the scene. However, since I set QGraphicsView::NoViewportUpdate, the line shouldn't be displayed. On the opposite, the line gets displayed.
According to the documentation, QGraphicsView will never update its viewport when the scene changes; the user is expected to control all updates. This mode disables all (potentially slow) item visibility testing in QGraphicsView, and is suitable for scenes that either require a fixed frame rate, or where the viewport is otherwise updated externally.
How do I solve this problem?
Here is the code:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QWidget>
#include <QPushButton>
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
QGraphicsView* view;
QGraphicsScene* scene;
QPushButton* b;
public slots:
void start();
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <QVBoxLayout>
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent)
{
scene = new QGraphicsScene(0, 0, 400, 400);
view = new QGraphicsView(scene);
view->setViewportUpdateMode(QGraphicsView::NoViewportUpdate);
b = new QPushButton("Start");
connect (b, &QPushButton::clicked, this, &MainWindow::start);
QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget(view);
layout->addWidget(b);
setLayout(layout);
}
MainWindow::~MainWindow()
{
}
void MainWindow::start()
{
scene->addLine(0, 0, 200, 200);
}
I "solved" that. I discovered the viewport doesn't get updated if you do NOT hover (for example) it with the mouse. So, if you do not interact, viewport does not update. However, viewport does not update if you scroll with the mouse wheel inside the qGraphicsView.

Qt QWidget::minimumSizeHint delay

I want to write dialog with "spoiler". When I show/hide some elements, the form must resize to match her content. But I noticed that minimumSizeHint works with some delay. For example I wrote following dialog code:
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
class Dialog : public QDialog {
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
private:
QPushButton *button1, *button2, *button3;
void adjust();
};
#endif // DIALOG_H
dialog.cpp
#include <QVBoxLayout>
#include <QPushButton>
#include <QTimer>
#include "dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent) {
QVBoxLayout *layout = new QVBoxLayout;
button1 = new QPushButton("bad method", this);
button2 = new QPushButton("working method", this);
button3 = new QPushButton("indent", this);
layout->addWidget(button1);
layout->addWidget(button2);
layout->addWidget(button3);
setLayout(layout);
connect(button1, &QPushButton::pressed, [=]() {
button3->setVisible(!button3->isVisible());
adjust();
});
connect(button2, &QPushButton::pressed, [=]() {
button3->setVisible(!button3->isVisible());
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, [=](){ adjust(); });
timer->setSingleShot(true);
timer->start(0);
});
}
void Dialog::adjust() {
resize(width(), minimumSizeHint().height());
}
When pressed button1, the form isn't match her content tightly, but when pressed button2, the form tightly match her content.
How to achieve results as pressing button2 without timers and other workarounds? Why button1 isn't work?
Consider that the minimum size is not computed until some events are processed in the event loop. If you don't want to use a timer, one workaround is to process the event loop for some iterations after the button is hidden or made visible, and then adjust size.
It's like :
button3->setVisible(!button3->isVisible());
for(int i=0;i<10;i++)
qApp->processEvents();
adjust();
A better solution is to call QLayout::activate() before resizing:
button3->setVisible(!button3->isVisible());
layout->activate();
adjust();
activate() forces your layout to recalculate the size hint.
I tried the QLayout::activate() solution, but it doesn't work for me.
QLayout::activate() returns false.
This is my code: this is a QMainWindow.
ui.groupBox->setVisible(!ui.groupBox->isVisible());
qDebug() << this->layout()->activate();
qDebug() << this->minimumSizeHint();
this->resize(this->minimumSizeHint());
any ideas why it's not working?
My current workaround is:
QTimer::singleShot(10, this, SLOT(on_resizeMin()));
but i noticed 10ms may not be enough on a slow system. Nasty workaround.

Letting QWidget and QVBoxLayout automatically resize if child resizes (Qt4)

I have the following minimal example code given.
main.cpp:
#include <QApplication>
#include "qt.h"
int main(int argc, char** argv)
{
QApplication app(argc, argv);
MyDialog mainWin;
mainWin.show();
return app.exec();
}
qt.cpp:
#include <QLabel>
#include "qt.h"
void MyDialog::setupUi()
{
setCentralWidget(new QWidget);
mainLayout = new QVBoxLayout( centralWidget() );
centralWidget()->setLayout(mainLayout);
// show the add new effect channel button
QPushButton* newKnobBtn = new QPushButton("new", this );
connect( newKnobBtn, SIGNAL(clicked()), this, SLOT(addNewKnob()));
mainLayout->addWidget( newKnobBtn, 0, Qt::AlignRight );
containerWidget = new QWidget(this);
scrollArea = new QScrollArea(containerWidget);
mainLayout->addWidget(containerWidget);
scrollLayout = new QVBoxLayout(scrollArea);
scrollArea->setLayout(scrollLayout);
/*
QSizePolicy pol;
pol.setVerticalPolicy(QSizePolicy::Expanding);
setSizePolicy(pol);
*/
addNewKnob(); // to fit size initially
}
void MyDialog::addNewKnob()
{
scrollLayout->addWidget(new QLabel("Hello World", this));
/*
containerWidget->adjustSize();
adjustSize();
*/
}
qt.h:
#include <QMainWindow>
#include <QVBoxLayout>
#include <QScrollArea>
#include <QPushButton>
class MyDialog : public QMainWindow
{
Q_OBJECT
private slots:
void addNewKnob();
private:
void setupUi();
QVBoxLayout* mainLayout;
QScrollArea* scrollArea;
QVBoxLayout* scrollLayout;
QWidget* containerWidget;
public:
MyDialog( ) { setupUi(); }
};
Compiling: Put all in one directory, type
qmake -project && qmake && make
I have the adjustSize() solution from here, but it does not work: (link). Everything I commented out was things I tried but did not help.
How do I make containerWidget and scrollLayout grow correctly, when a new Label is being added to scrollLayout?
Here's a simplified version that works for me:
qt.cpp:
#include <QLabel>
#include <QPushButton>
#include <QScrollArea>
#include "qt.h"
MyDialog::MyDialog()
{
QWidget * mainWidget = new QWidget;
QBoxLayout * mainLayout = new QVBoxLayout(mainWidget);
setCentralWidget(mainWidget);
// show the add new effect channel button
QPushButton* newKnobBtn = new QPushButton("new");
connect( newKnobBtn, SIGNAL(clicked()), this, SLOT(addNewKnob()));
mainLayout->addWidget( newKnobBtn, 0, Qt::AlignRight );
QScrollArea * scrollArea = new QScrollArea;
scrollArea->setWidgetResizable(true);
mainLayout->addWidget(scrollArea);
QWidget * labelsWidget = new QWidget;
labelsLayout = new QVBoxLayout(labelsWidget);
scrollArea->setWidget(labelsWidget);
addNewKnob(); // to fit size initially
}
void MyDialog::addNewKnob()
{
labelsLayout->addWidget(new QLabel("Hello World"));
}
qt.h:
#include <QMainWindow>
#include <QBoxLayout>
class MyDialog : public QMainWindow
{
Q_OBJECT
public:
MyDialog( );
private slots:
void addNewKnob();
private:
QBoxLayout * labelsLayout;
};
You have containerWidget that contain only one QScrollArea. I don't know why do you need this. But if you need this for some reason, you need to add a layout to this widget in order to make layouts work. Also do not create a layout for QScrollArea. It already have internally implemented layout. You should add scrollLayout to the scroll area's viewport() widget instead.
When you construct a layout and pass a widget to its constructor, the layout is automatically assigned to the passed widget. You should not call setLayout after that. This action will take no effect and produce console warning.