How to center items in a QBoxLayout without using QSpacerItems - c++

A QLayout always tries to use the whole available space of the QWidget. But, sometimes, you want it to keep it's minimal size and center itself on the widget...because you feel like it looks better (if the layout only contains items that does not make sense to be displayed bigger than their default size: QLabel, QPushButton and stuffs like that).
I had this problem hundreds of times in the past, and I'm always adding stupid QSpacerItems everywhere to fix that. I'm wondering if there could be a better solution.
I isolated the problem as an illustration:
#include <QApplication>
#include <QDialog>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QSpacerItem>
class MainFrame : public QDialog
{
public:
MainFrame() : QDialog(NULL)
{
QHBoxLayout* theLayout = new QHBoxLayout();
setLayout( theLayout );
// don't want to add spacers all the time!
//theLayout->addSpacerItem( new QSpacerItem( 10, 10, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
theLayout->addWidget( new QLabel( "A text", this ) );
theLayout->addWidget( new QPushButton( "A button", this ) );
// don't want to add spacers all the time!
//theLayout->addSpacerItem( new QSpacerItem( 10, 10, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ) );
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainFrame w;
w.resize(400,400);
w.show();
return a.exec();
}
Without the spacers, here's how it looks like:
I hate this display, on larger QWidget your eyes have to go all around the widget to find the relevant information.
With spacers, here's how it looks like:
I like it much better.
Is there a way to do this without adding the spacers? It's a pain adding them and always having to specify all those arguments (defaults won't do what you want...).
I tried to use QLayout::setSizeConstraint with no success. Does not appear to be meant to do this.

I would do it in the following way:
[..]
theLayout->addStretch();
theLayout->addWidget( new QLabel( "A text", this ) );
theLayout->addWidget( new QPushButton( "A button", this ) );
theLayout->addStretch();
[..]

Related

Draw inside a QGraphicsScene inside a QWidget

I am trying to have a window (in the form of a QWidget) consisting of both a menu on the right and a graphics area on the left.
Despite the numerous websites explaining the many ways to use QGraphicsScene and QGraphicsView, I just couldn't figure out how to do it.
Here's main.cpp modified to work on its own :
#include <QApplication>
#include <QRectF>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QPushButton>
#include <QVBoxLayout>
#include <QGraphicsRectItem>
#include <QPalette>
int main (int argc, char * argv []) {
QApplication app (argc, argv) ;
// Main window
QWidget frame ;
frame.setFixedSize(750, 550) ;
frame.show() ;
// Right side, works ok
QWidget menu (&frame) ;
menu.setFixedSize(200, 500) ;
menu.move(550, 10) ;
QPalette pal = menu.palette() ;
pal.setColor(QPalette::Background, Qt::red) ;
menu.setPalette(pal) ; // I expected this to color the whole area
// to show the extent of the zone devoted to the menu,
// but it only outlines the button
menu.show() ;
QPushButton button ("Menu", &menu) ;
button.show() ; // I didn't think this was necessary,
// but the button didn't appear without this line
// Left side, nothing displayed
QVBoxLayout layout ;
QGraphicsScene * scene = new QGraphicsScene () ;
QGraphicsView view (scene) ;
layout.addWidget(&view) ;
QWidget canvas (&frame) ;
canvas.setLayout(&layout) ;
// I found this trick to include a QGraphicsScene inside a QWidget,
// I haven't had the opportunity to see whether it really works.
scene->addItem(new QGraphicsRectItem(10, 10, 20, 20)) ;
// The above line has no visible effect
view.show() ;
return app.exec() ;
}
I would expect this to create a window, put a bunch of buttons on the right side (or in the case of the rediced code I provided, just a single button), and draw a rectangle on the left, but it leaves the whole left area blank.
Does the problem come from how I put the QGraphicsView inside the QWidget ? Or is it failing to draw because of something else ? Do I have to update the QGraphicsView to reflect the change ? Is it just out of visible range ?
Finally, is the failure to draw in any way related to the fact that the whole application crashes on line QWidget canvas (&frame) ; when closed ?
I just repaired you program a bit, so that it illustrates, what you can do with Qt and how you should likely use the framework.
I just moved the QPushButton to a QAction residing in a QMenuBar. The QMenuBar can be added to a QMainWindow, which is reasonable for a normal app.
The central widget of the QMainWindow contains the QGraphicsView. Now, you just forgot to connect the QGraphicsScene with the QGraphicsView. That was the reasons for not seeeing anything in your view.
QGraphicsView and QGraphicsScene are just a typical example for a MVC pattern. You can also add another QGraphicsView and connect it to the same QGraphicsScene.
You should also create all you objects with new, as Qt automatically disposes all its children of a QObject, if it is either deleted or leaves scope.
If you are realyl interesting into seriously learning Qt I suggest, that you are creating plenty small example programs like these. It really helped me a lot.
#include <QApplication>
#include <QMenuBar>
#include <QGraphicsView>
#include <QVBoxLayout>
#include <QGraphicsRectItem>
#include <QMainWindow>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
auto mainWindow = new QMainWindow;
auto menuBar = new QMenuBar;
auto menu = new QMenu("Menu");
auto action = new QAction("Action");
menu->addAction(action);
menuBar->addMenu(menu);
mainWindow->setMenuBar(menuBar);
auto frame = new QFrame;
frame->setLayout(new QVBoxLayout);
mainWindow->setCentralWidget(frame);
auto scene = new QGraphicsScene();
auto view=new QGraphicsView(scene);
view->setScene(scene); // That connects the view with the scene
frame->layout()->addWidget(view);
QObject::connect(action, &QAction::triggered, [&]() {
scene->addItem(new QGraphicsRectItem(10, 10, 20, 20));
});
mainWindow->show();
return app.exec();
}

Vertical size of a QLabel with wordWrap enabled

I have a QLabel in a QVBoxLayout. Most of the times, it only has one line of text, but sometimes, the text can be too long to fit in one line. So I have to enable wordWrap.
I want the label to be as (vertically) small as possible, thus I set setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Maximum).
Now, if there's enough vertical space, the label is higher as it would have to be with only one line:
At the same window size and without wordWrap being enabled, the label only takes the minimum space I would like it to take:
Can this also be achieved with wordWrap being enabled and independent of the window height?
I tried to reproduce the behavior with a small example. Maybe this might help you to solve your issue. Just enlarge the widget and type some random text having several words separated by white spaces.
The idea is to use the correct combination of QSizePolicys not just for the QLabel, but also for the other GUI elements.
#include <QFrame>
#include <QLabel>
#include <QGroupBox>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QPushButton>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
auto frame = new QFrame;
frame->setLayout(new QVBoxLayout);
auto groupEdit = new QGroupBox;
groupEdit->setLayout(new QHBoxLayout);
auto edit = new QLineEdit;
groupEdit->layout()->addWidget(edit);
frame->layout()->addWidget(groupEdit);
auto group = new QGroupBox;
frame->layout()->addWidget(group);
group->setLayout(new QHBoxLayout);
auto label = new QLabel;
groupEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
group->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
group->layout()->addWidget(label);
group->layout()->addWidget(new QPushButton);
QObject::connect(edit, &QLineEdit::textEdited, [&](const QString& text) {
label->setText(text);
label->setWordWrap(true);
});
frame->show();
return a.exec();
}

Why am I getting QWindowsWindow::setGeometry: Unable to set geometry warning with Qt 5.12.0

I migrated some code from Qt 5.6.0 to 5.12.0. Suprisingly, I'm getting lots of warnings related to QWindowsWindow::setGeometry. Whenever a dialog is shown on top of another, I get this warning.
I could isolate the problem in a MCVE, it's very simple and minimal, all parenting look good, however, we get the warning when button is pressed:
QWindowsWindow::setGeometry: Unable to set geometry 132x30+682+303 on QWidgetWindow/'QDialogClassWindow'. Resulting geometry: 132x42+682+303 (frame: 4, 28, 4, 4, custom margin: 0, 0, 0, 0, minimum size: 116x42, maximum size: 16777215x16777215).
main.cpp:
#include <QApplication>
#include "mainframe.h"
#include <qDebug>
void MessageOutput( QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
qDebug() << msg;
}
int main( int argc, char* argv[] )
{
QApplication app(argc, argv);
qInstallMessageHandler(MessageOutput);
MainFrame wnd;
wnd.show();
return app.exec();
}
mainframe.h:
#include <QMainWindow>
class QPushButton;
class MainFrame : public QMainWindow
{
Q_OBJECT
public:
MainFrame();
public slots:
void showPopup();
private:
QPushButton* button;
};
mainframe.cpp:
#include "mainframe.h"
#include <QDialog>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
MainFrame::MainFrame()
{
QWidget* widget = new QWidget( this );
widget->setLayout( new QVBoxLayout( widget ) );
QPushButton* pTextButton = new QPushButton( "Show popup", widget );
widget->layout()->addWidget( pTextButton );
connect( pTextButton, SIGNAL(clicked()), this, SLOT(showPopup()) );
setCentralWidget( widget );
}
void MainFrame::showPopup()
{
QDialog dlg( this );
dlg.setLayout( new QVBoxLayout() );
dlg.layout()->addWidget( new QLabel("popup message",&dlg) );
dlg.exec();
}
I see the issue under Windows 7 and 10. Am I doing anything wrong?
I know the warning can be removed by setting setMinimumSize (see https://stackoverflow.com/a/31231069/3336423), but why should we do this for every widget we create? Is there a way to fix that for good?
As you mentioned, this problem occurs only in Windows: the QWindowsWindow class is part of the windows platform plugin. Looking at Qt's source code (qwindowswindow.cpp#QWindowsWindow::setGeometry) there is no direct way to pause that specific message.
The only global solution I can think of right now is to filter the warning messages using a message handler:
void myMessageOutput(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{
if (type != QtWarningMsg || !msg.startsWith("QWindowsWindow::setGeometry")) {
QByteArray localMsg = msg.toLocal8Bit();
fprintf(stdout, localMsg.constData());
}
}
int main(int argc, char* argv[])
{
qInstallMessageHandler(myMessageOutput);
QApplication a(argc, argv);
// ...
}
UPDATE
One of the problems is that Windows adds its own buttons to the frame. In your example the dialog adds three buttons: the system button (the icon, top-left corner), the help button and the close button. The help and close buttons have a fixed size, which happens to be larger than the QDialog's frame (which is computed as the maximum between the requested size and minimumSize). This then generates the warning: your requested size doesn't match the one created by Windows:
If you remove the help button, for example (dlg.setWindowFlags(dlg.windowFlags() & ~Qt::WindowContextHelpButtonHint);), the warning disappears without setting a minimum size for the window. A manual action must be taken for each dialog displayed, but I think it is easier to automatize than the minimum size (through a factory maybe?):
The issue was reported to Qt:
https://bugreports.qt.io/browse/QTBUG-73258
To the code in OP is OK, it's just a Qt bug.
It's marked as "P2 Important", so hopefully it should be fixed in a next release.
Update: It's still not fixed in Qt 6.2.2...

QScrollArea missing Scrollbar

I think it is the same problem as : QScrollArea resizing QWidget
but there are not solution. so let me expose the problem.
test 2 inherited from QWidget:
composed :
vector of QSpinBox
QScrollArea
QVBoxLayout
test2 (QWidget) <- QScrollArea <- QVBoxLayout <- Spinbox
Problems :
There are no scrollbar
[FIXED] The inside of the scrollbar is shrinked to fit so little space nothing can be read (the window can be resized during execution that will cause the inside to get bigger and be readable nevertheless no scrollbar will appear)
I Think problems come from a single source :: Size Hints and Layouts (http://qt-project.org/doc/qt-5.1/qtwidgets/qscrollarea.html#details)
The second problem (shrinked widget) can be solved by setting "c->setSizeConstraint(QLayout::SetMinimumSize);"
I am currently seeking a solution for the missing scrollbar
here is a code showing my problem :
<c++>
#include <QWidget>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QSpinBox>
class test2 : public QWidget
{
Q_OBJECT
public:
test2(QWidget *parent = 0) :QWidget(parent)
{
b = new QScrollArea(this);
c = new QVBoxLayout;
for (int i = 0; i < 10; i++)
{
a.push_back(new QSpinBox());
c->addWidget(a[i]);
}
c->setSizeConstraint(QLayout::SetMinimumSize);
b->setLayout(c);
b->resize(200, 200);
}
~test2()
{
for (int i = 0; i < 10; i++)
delete a[i];
}
protected:
QVector<QSpinBox*> a;
QScrollArea* b;
QVBoxLayout* c;
};
int main(int argc, char *argv[])
{
///*
QApplication app(argc, argv);
test2 a;
a.show();
return app.exec();//*/
}
EDIT :: found a Solution here:
http://qt-project.org/forums/viewthread/295
if you don't want to read huge amount of useless code here what he has done ::
he warped the layout inside a widget
Solution :: inherit the Object from ScrollBar <- Widget <- Layout
instead of widget <- ScrollBar <- Layout
but it a work around not really a solution...
I going to try on the example I gave.
it works. Does anyone have a better solution ??
You do not want to set the layout on the scroll area itself. The answer you cite stems from misunderstanding this.
You need to have a widget within a scrollarea, and you pass that widget to the area using QScrollArea::setWidget. If all you have inside the scroll area is one widget with no children, then you don't need additional layout.
You do not need to manually keep track of widgets that are owned by a layout. They'll be deleted automatically once the widget that has the layout is deleted.
The QScrollArea widget is not laid out within its enclosing widget.
Below is a working example of how to do it:
// https://github.com/KubaO/stackoverflown/tree/master/questions/scroll-18703286
#include <QScrollArea>
#include <QVBoxLayout>
#include <QSpinBox>
#include <QApplication>
class Window : public QWidget
{
QVBoxLayout m_layout{this};
QScrollArea m_area;
QWidget m_contents;
QVBoxLayout m_contentsLayout{&m_contents};
QSpinBox m_spinBoxes[10];
public:
Window(QWidget *parent = {}) : QWidget(parent) {
m_layout.addWidget(&m_area);
m_area.setWidget(&m_contents);
for (auto & spinbox : m_spinBoxes)
m_contentsLayout.addWidget(&spinbox);
m_contentsLayout.setSizeConstraint(QLayout::SetMinimumSize);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Window w;
w.show();
return app.exec();
}

How do I get the currently visible text from a QTextEdit or QPlainTextEdit widget?

It seems like this would be a common thing to do, but I can't find how.
I have a QTextEdit or QPlainTextEdit widget with a bunch of text. Enough that scrolling is necessary.
I want another widget to give some information about the currently visible text. To do this, I need to know
when the visible text changes
what's the text?
I see that QPlainTextEdit has the method firstVisibleBlock, but it's protected. This tells me that it's not really something I should be using in my application. I wouldn't otherwise need to subclass from the edit window.
I also see that there's the signal updateRequest but it's not clear what I do with the QRect.
How do I do it or where do I find a hint?
I've written a minimal program that as two QTextEdit fields. In the left field you write and the text you are writing is shown in the second text edit too. You get the text of a QTextEdit by using toPlainText() and the signal is textChanged().
I've tested it and what you write in m_pEdit_0 is shown in "real-time" in m_pEdit_1.
main_window.hpp
#ifndef __MAIN_WINDOW_H__
#define __MAIN_WINDOW_H__
#include <QtGui/QtGui>
#include <QtGui/QMainWindow>
#include <QtGui/QApplication>
class main_window : public QMainWindow
{
Q_OBJECT
public:
main_window( QWidget* pParent = 0 );
~main_window();
public Q_SLOTS:
void on_edit_0_text_changed();
private:
QHBoxLayout* m_pLayout;
QTextEdit* m_pEdit_0;
QTextEdit* m_pEdit_1;
};
#endif // !__MAIN_WINDOW_H__
main_window.cpp
#include "main_window.hpp"
main_window::main_window( QWidget *pParent ) : QMainWindow( pParent )
{
m_pEdit_0 = new QTextEdit( this );
m_pEdit_1 = new QTextEdit( this );
connect( m_pEdit_0, SIGNAL( textChanged() ), this, SLOT( on_edit_0_text_changed() ) );
m_pLayout = new QHBoxLayout;
m_pLayout->addWidget( m_pEdit_0 );
m_pLayout->addWidget( m_pEdit_1 );
QWidget* central_widget = new QWidget( this );
central_widget->setLayout( m_pLayout );
setCentralWidget( central_widget );
}
main_window::~main_window()
{
}
void main_window::on_edit_0_text_changed()
{
m_pEdit_1->setText( m_pEdit_0->toPlainText() );
}
main.cpp
#include "main_window.hpp"
int main( int argc, char* argv[] )
{
QApplication a(argc, argv);
main_window mw;
mw.show();
return a.exec();
}
Edit:
This would work too, but would lack in performance for huge documents:
void main_window::on_edit_0_text_changed()
{
QStringList text_in_lines = m_pEdit_0->toPlainText().split( "\n" );
m_pEdit_1->clear();
for( int i = 0; i < text_in_lines.count(); i++ )
{
m_pEdit_1->append( text_in_lines.at( i ) );
}
}