Qt making a QPushButton fill layout cell - qt

I need a pushbutton to either fill or not fill the entire space provided by a QGridLayout cell upon the creation of the button (the alignment value is loaded from file). I've simplified my situation with the following code. During run-time, users can set the alignment of the button - either making it fill the entire layout cell or nicely centered. It works so long as the button didn't start off with NULL alignment specified. Yet, I need the ability to start off with a NULL alignment (i.e. the button fills the space of the layout cell). When initially aligning with NULL, what is getting set to make the button lock into a AlignVCenter setting and how can I get the button to return to acting like it was initialized with something other than null alignment?
I'm using Qt 4.8 on Ubuntu 12.04 LTS
#include <QPushButton>
#include <QGridLayout>
#include <QMainWindow>
#include <QApplication>
class MyWidget : public QWidget {
Q_OBJECT
QPushButton* m_pb;
QGridLayout* m_gl;
protected slots:
void pbClicked();
public:
MyWidget(QWidget* parent = 0);
};
MyWidget::MyWidget(QWidget* parent): QWidget(parent)
{
m_pb = new QPushButton(tr("push me"));
connect(m_pb, SIGNAL(clicked()), this, SLOT(pbClicked()));
m_gl = new QGridLayout();
//use (1) to see button expand when button is pressed
//use (2) to show that I can't start off expanded
/*1*/ //m_gl->addWidget(m_pb, 0, 0, Qt::AlignCenter); // creates desired effect
/*2*/ //m_gl->addWidget(m_pb, 0, 0, 0); //does not create desired effect
setLayout(m_gl);
}
void MyWidget::pbClicked(){
//will expand button so long as initial alignment is not NULL
m_gl->setAlignment(m_pb, 0);
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MyWidget* widget = new MyWidget();
QMainWindow window;
window.setCentralWidget(widget);
window.show();
return app.exec();
}
#include "main.moc"

The "desired" behavior that you see is in fact an error, and I will file a bug report for it. Thanks for spotting it - nice corner case.
You need to set the size policy of the button to expanding in both directions. Buttons normally don't want to expand vertically, so if you tried a variant that toggles the alignment, you'd see that it works only horizontally, and that's correct.
This is a simple demonstration that shows the correct behavior that also fulfills your needs.
#include <QPushButton>
#include <QGridLayout>
#include <QApplication>
class AlignButton : public QPushButton {
Q_OBJECT
Qt::Alignment m_alignment;
Q_SLOT void clicked() {
m_alignment ^= Qt::AlignCenter;
parentWidget()->layout()->setAlignment(this, m_alignment);
label();
}
void label() {
setText(QString("Alignment = %1").arg(m_alignment));
}
public:
AlignButton(Qt::Alignment alignment, QWidget * parent = 0) :
QPushButton(parent),
m_alignment(alignment)
{
connect(this, SIGNAL(clicked()), SLOT(clicked()));
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
label();
}
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget window;
QGridLayout layout(&window);
layout.addWidget(new AlignButton(0), 0, 0, 0);
layout.addWidget(new AlignButton(Qt::AlignCenter), 1, 0, Qt::AlignCenter);
window.setMinimumSize(500, 200);
window.show();
return app.exec();
}
#include "main.moc"

Related

Fixed text inside or adjacent to QProgressBar with scaling font size in Qt

I am new with Qt (using Qt Creator) and the QProgressBar. I am interested in learning how to have a fixed text value (not the value of the progress bar) inside or adjacent to the left of a QProgressBar and have its font size scale according with the size of the progress bar.
For example:
or
I have considered using a QLabel but failed and I could not find any examples online.
Any code sample illustrating the solution for me to understand and learn from will be much appreciated.
If label inside the progressbar will do, then here is an example. This might not be exactly what you want, but it should send you in the right direction. I adjust the font size in the resize event. In this example the font size is calculated based on the size of the label, which is the same size as the progress bar.
#include <QApplication>
#include <QProgressBar>
#include <QWidget>
#include <QLabel>
#include <QLayout>
#include <QTimer>
class Widget : public QWidget
{
Q_OBJECT
QProgressBar progressBar;
QLabel *label;
public:
Widget(QWidget *parent = nullptr) : QWidget(parent)
{
progressBar.setRange(0, 100);
progressBar.setValue(20);
progressBar.setTextVisible(false);
progressBar.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
label = new QLabel(&progressBar);
label->setText("Hello World!");
setLayout(new QHBoxLayout);
layout()->addWidget(&progressBar);
}
protected:
void resizeEvent(QResizeEvent *)
{
label->resize(progressBar.size());
QFontMetrics fm(label->font());
float multiplier_horizontal = (float)label->width() / fm.width(label->text());
float multiplier_vertical = (float)label->height() / fm.height();
QFont font = label->font();
font.setPointSize(font.pointSize() * qMin(multiplier_horizontal, multiplier_vertical));
label->setFont(font);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main.moc"

Reparenting a Qt widget

I have a WidgetA widget, which is an owner-drawn widget. It's currently placed in QMainWindow's QVBoxLayout. After clicking a button, I'd like to "detach" WidgetA from this QVBoxLayout, insert QSplitter into this QVBoxLayout and "readd" WidgetA to this QSplitter. All this without destroying WidgetA, so it will preserve its drawing context, etc.
So, currently I have this (only one widget in a window):
I'd like to put a QSplitter between WidgetA and QMainWindow, and create a new widget, WidgetB, so I'd end up with:
Later I'd like it to split even further, so both WidgetA and WidgetB would still allow themselves to be detached and placed in a new QSplitter, so it would be possible to create f.e. this hierarchy:
And, to be complete, one more step:
I'm not very experienced in Qt, so what I'm trying to do may seem pretty obvious, but I couldn't find how to "reparent" widgets. Is this possible in Qt?
Please, see reparent example, may be it helps you:
//MyMainWindow.h
#include <QWidget>
#include <QPainter>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QSplitter>
class MyWidget: public QWidget
{
public:
MyWidget(QWidget* parent, int number)
: QWidget(parent),
m_number(number)
{
}
private:
virtual void paintEvent(QPaintEvent* e)
{
QWidget::paintEvent(e);
QPainter p(this);
p.fillRect( rect(), Qt::red);
p.drawText( rect(), Qt::AlignCenter, QString::number(m_number) );
}
private:
int m_number;
};
class MyMainWindow: public QWidget
{
Q_OBJECT
public:
MyMainWindow()
{
setFixedSize(300, 200);
m_mainLayout = new QVBoxLayout(this);
QHBoxLayout* buttonLayout = new QHBoxLayout;
m_mainLayout->addLayout(buttonLayout);
m_button = new QPushButton("Button", this);
buttonLayout->addWidget(m_button);
connect(m_button, SIGNAL(clicked()), this, SLOT(onButtonClickedOnce()));
m_initWidget = new MyWidget(this, 1);
m_mainLayout->addWidget(m_initWidget);
}
private slots:
void onButtonClickedOnce()
{
m_button->disconnect(this);
m_mainLayout->removeWidget(m_initWidget);
QSplitter* splitter = new QSplitter(Qt::Horizontal, this);
m_mainLayout->addWidget(splitter);
splitter->addWidget(m_initWidget);
MyWidget* newWidget = new MyWidget(splitter, 2);
splitter->addWidget(newWidget);
}
private:
QVBoxLayout* m_mainLayout;
QWidget* m_initWidget;
QPushButton* m_button;
};
//main.cpp
#include <QtWidgets/QApplication>
#include "MyMainWindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyMainWindow mainWindow;
mainWindow.show();
return a.exec();
}
When you operate with widget which is part of layout, then you need to use appropriate methods of QLayout (parent of QVBoxLayout) to detach the item from layout:
QLayout::removeWidget (removeItem if it is not widget, but spacer item or another layout)
QLayout::addWidget (addItem --/--)
Btw: even when widget moves between layouts, its parent may even stay same. I guess you have no need to call QWidget::setParent() as the calls of addWidget/removeWidget will do all work for you.

QT QLabel (used as an image container) fullscreen bug

A experienced the following bug in Qt 4.8.5, under Ubuntu 13.04 (and I'm nem to Qt)
I have have an application with the following structure:
Mainwondow
-CentralWidget
--VerticalLayout
---TabWidget
---QLabel (created with code, and added to the layout)
---StatusBar
In fullscreen mode I hide the TabWidget, and the Statusbar, then the QLabel stops refreshing. (i have a thread to do the refresh) The strange thing is, when i restore the TabWidget or the StatusBar it works fine. It also works good, if i add a 1x1 pixel label to the VerticalLayout.
The slot responsible for the gui change;
void Mainview::onToggleFullScreen()
{
if (this->isFullScreen())
{
this->showNormal();
this->statusbar->show();
this->tabWidget->show();
}
else
{
this->showFullScreen();
this->statusbar->hide();
this->tabWidget->hide();
}
}
But the thing I cant understand if I put a QLabel near the image, it works, and if I add this single line to the MainWindow constructor, it stops refreshing:
label_10->hide(); //this is the label
Any idea what is the problem?
(Thanks in advance)
You're probably doing it in some wrong way, but you don't show the code, so how can we know?
Below is a safe SSCCE of how one might do it. Works under both Qt 4.8 and 5.1.
Nitpick: The status bar should not be a part of the centralWidget()! QMainWindow provides a statusBar() for you.
The only safe way of passing images between threads is via QImage. You can not use QPixmap anywhere but in the GUI thread. End of story right there.
In the example below, all of the important stuff happens behind the scenes. The DrawThing QObject lives in another thread. This QThread's default implementation of the run() method spins a message loop. That's why the timer can fire, you need a spinning message loop for that.
Every time the new image is generated, it is transmitted to the GUI thread by implicitly posting a message to MainWindow. The message is received by Qt event loop code and re-synthesized into a slot call. This is done since the two ends of a connection (DrawThing and MainWindow instances) live in different threads.
That the beauty of Qt's "code less, create more" approach to design :) The more you leverage what Qt does for you, the less you need to worry about the boilerplate.
//main.cpp
#include <QMainWindow>
#include <QVBoxLayout>
#include <QStatusBar>
#include <QLabel>
#include <QThread>
#include <QPainter>
#include <QImage>
#include <QApplication>
#include <QBasicTimer>
#include <QPushButton>
class DrawThing : public QObject {
Q_OBJECT
int m_ctr;
QBasicTimer t;
void timerEvent(QTimerEvent * ev) {
if (ev->timerId() != t.timerId()) return;
QImage img(128, 128, QImage::Format_RGB32);
QPainter p(&img);
p.translate(img.size().width()/2, img.size().height()/2);
p.scale(img.size().width()/2, img.size().height()/2);
p.eraseRect(-1, -1, 2, 2);
p.setBrush(Qt::NoBrush);
p.setPen(QPen(Qt::black, 0.05));
p.drawEllipse(QPointF(), 0.9, 0.9);
p.rotate(m_ctr*360/12);
p.setPen(QPen(Qt::red, 0.1));
p.drawLine(0, 0, 0, 1);
m_ctr = (m_ctr + 1) % 12;
emit newImage(img);
}
public:
explicit DrawThing(QObject *parent = 0) : QObject(parent), m_ctr(0) { t.start(1000, this); }
Q_SIGNAL void newImage(const QImage &);
};
class MainWindow : public QMainWindow {
Q_OBJECT
QLabel *m_label;
public:
explicit MainWindow(QWidget *parent = 0, Qt::WindowFlags flags = 0) : QMainWindow(parent, flags) {
QWidget * cw = new QWidget;
QTabWidget * tw = new QTabWidget();
QVBoxLayout * l = new QVBoxLayout(cw);
l->addWidget(tw);
l->addWidget(m_label = new QLabel("Label"));
setCentralWidget(cw);
QPushButton * pb = new QPushButton("Toggle Status Bar");
tw->addTab(pb, "Tab 1");
connect(pb, SIGNAL(clicked()), SLOT(toggleStatusBar()));
statusBar()->showMessage("The Status Bar");
}
Q_SLOT void setImage(const QImage & img) {
m_label->setPixmap(QPixmap::fromImage(img));
}
Q_SLOT void toggleStatusBar() {
statusBar()->setHidden(!statusBar()->isHidden());
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QThread t;
DrawThing thing;
MainWindow w;
thing.moveToThread(&t);
t.start();
w.connect(&thing, SIGNAL(newImage(QImage)), SLOT(setImage(QImage)));
w.show();
t.connect(&a, SIGNAL(aboutToQuit()), SLOT(quit()));
int rc = a.exec();
t.wait();
return rc;
}
#include "main.moc"

How to add a tick mark to a slider if it cannot inherit QSlider

I have a Qt dialog and there is a slider in it, when the dialog is initialized the slider will be set a value. In order to remind the user what is the default value, I want to add a mark to the slider, just draw a line or a triangle above the handle. Here, the slider should be of QSlider type, that means I can't implement a customized control derived from QSlider. Is there any way to realize it ?
I'm not clear why you can't derive a control from QSlider. You can still treat it like a QSlider, just override the paintEvent method. The example below is pretty cheesy, visually speaking, but you could use the methods from QStyle to make it look more natural:
#include <QtGui>
class DefaultValueSlider : public QSlider {
Q_OBJECT
public:
DefaultValueSlider(Qt::Orientation orientation, QWidget *parent = NULL)
: QSlider(orientation, parent),
default_value_(-1) {
connect(this, SIGNAL(valueChanged(int)), SLOT(VerifyDefaultValue(int)));
}
protected:
void paintEvent(QPaintEvent *ev) {
int position = QStyle::sliderPositionFromValue(minimum(),
maximum(),
default_value_,
width());
QPainter painter(this);
painter.drawLine(position, 0, position, height());
QSlider::paintEvent(ev);
}
private slots:
void VerifyDefaultValue(int value){
if (default_value_ == -1) {
default_value_ = value;
update();
}
}
private:
int default_value_;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
DefaultValueSlider *slider = new DefaultValueSlider(Qt::Horizontal);
slider->setValue(30);
QWidget *w = new QWidget;
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(slider);
layout->addStretch(1);
w->setLayout(layout);
QMainWindow window;
window.setCentralWidget(w);
window.show();
return app.exec();
}
#include "main.moc"
Easiest way I can think off is:
Add QSlider to QSlider (like you do it with layouts and QFrames). Slider above will be your current slider (clickable one). Slider below will be your "default tick position" value.
#include <QApplication>
#include <QSlider>
#include <QVBoxLayout>
int main(int argc, char * argv[])
{
QApplication app(argc, argv);
QSlider * defaultValueSlider = new QSlider();
QSlider * valueSlider = new QSlider(defaultValueSlider);
QVBoxLayout * lay = new QVBoxLayout(defaultValueSlider);
lay->setContentsMargins(0, 0, 0, 0);
lay->setSpacing(0);
lay->addWidget(valueSlider);
defaultValueSlider->setRange(0, 100);
valueSlider->setRange(0, 100);
defaultValueSlider->setValue(30);
defaultValueSlider->show();
return app.exec();
}
Why do you need to inherit a QSlider to access its public methods?
http://doc.trolltech.com/4.7/qslider.html
You can just call its setTickPosition() in your app.

'Magical' QTextEdit size

Here is an equivalent extracted code:
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QTextBrowser>
#include <QTextEdit>
class ChatMessageEdit : public QTextEdit {
public:
ChatMessageEdit(QWidget* parent) : QTextEdit(parent) { }
virtual QSize sizeHint() const { return QSize(0, 25); }
};
int main(int argc, char** argv) {
QApplication app(argc, argv);
QWidget* widget = new QWidget;
QVBoxLayout* layout = new QVBoxLayout;
QTextBrowser* log = new QTextBrowser(widget);
layout->addWidget(log, 1);
ChatMessageEdit* editor = new ChatMessageEdit(widget);
editor->setMinimumHeight(editor->sizeHint().height()); // empty
layout->addWidget(editor);
widget->setLayout(layout);
widget->show();
return app.exec();
}
The minimum size for editor is 25px, and so is it's minimal size. But by some strange reason it is created with a size about 100px that is always preferred to my size hint. Everything other is working as expected: expanding (size hint isn't really fixed in my application), shrinking etc. I tried changing size policy, but with abolutely no result.
This was the minumumSizeHint() method. I overloaded it to return sizeHint(), and everything is working as expected now.
You are also overlooking how layouts work. Please read up here on why your sizes are not being respected in a layout.

Resources