How to draw line behind buttons in Qt? - c++

Question
In a custom widget, I'd like to draw lines (using QPainter) that connect buttons in a QGridLayout. The lines shall be behind the buttons, in order to
a) not interfere with buttons in between
b) allow starting the lines in the center of the buttons, not the edges
Considering ideas from this question, I could almost realize a simple, basic version running in my GUI application (source code below).
As long as I use QPushButton with the standard Qt style, it works like a charm (left), but, as I want to use a custom style, the lines overlap (right):
What property or mechanism is causing this behavior?
Code
MyFrame.h:
#include <QFrame>
class MyFrame : public QFrame
{
public:
MyFrame();
virtual ~MyFrame() = default;
};
MyFrame.cpp:
#include "MyFrame.h"
#include "LineDrawWidget.h"
#include <QVBoxLayout>
#include <QGridLayout>
#include <QPushButton>
#include <QButtonGroup>
MyFrame::MyFrame()
{
auto* mainLayout = new QVBoxLayout(this);
auto* buttonLayout = new QGridLayout();
QPushButton* button;
auto* buttons = new QButtonGroup();
for (int i = 0; i < 3; ++i) {
button = new QPushButton();
button->setText(QString::number(i+1));
button->setFixedHeight(40);
button->setFixedWidth(40);
button->setStyleSheet("QPushButton { color : black; background-color : white; }");
button->setStyleSheet("QPushButton { border-style : outset; border-color: black; border-width: 2px; border-radius: 6px; }");
buttonLayout->addWidget(button);
buttons->addButton(button, i);
}
auto* lineDraw = new LineDrawWidget(
buttons->button(0),
buttons->button(2));
lineDraw->setLayout(buttonLayout);
mainLayout->addWidget(lineDraw);
}
LineDrawWidget.h:
#include <QWidget>
class LineDrawWidget : public QWidget
{
public:
LineDrawWidget(
QWidget* from,
QWidget* to,
QWidget* parent = nullptr);
virtual ~LineDrawWidget() = default;
protected:
virtual void paintEvent(QPaintEvent* e) final override;
private:
QWidget* _from;
QWidget* _to;
};
LineDrawWidget.cpp:
#include "LineDrawWidget.h"
#include <QPainter>
LineDrawWidget::LineDrawWidget(
QWidget* from,
QWidget* to,
QWidget* parent) :
QWidget(parent),
_from(from),
_to(to)
{
}
void LineDrawWidget::paintEvent(QPaintEvent* e)
{
(void)e;
QPainter painter(this);
QPoint start = _from->mapToGlobal(_from->rect().bottomLeft());
QPoint end = _to->mapToGlobal(_to->rect().topRight());
painter.drawLine(mapFromGlobal(start), mapFromGlobal(end));
}

I think the problem is the two separate calls to setStyle for a single QPushButton -- the second call appears to reset all properties not present in it. Try putting it all in a single call...
button->setStyleSheet("color : black; background-color : white; border-style : outset; border-color: black; border-width: 2px; border-radius: 6px;");
Seems to work for me.

Related

QSlider in QT misbehaves in new MacOS Monterey (v12.0.1) . Any workaround?

As reported here (https://bugreports.qt.io/browse/QTBUG-98093), QSlider component in QT is not working well in the new MacOS update.
If I add two or more horizontal sliders in the same window, dragging the grip in one slider affects the other ones. It may cause all of them to move together or may make the next one jump to an unexpected position.
This code below can reproduce the issues:
#include <QApplication>
#include <QDialog>
#include <QVBoxLayout>
#include <QSlider>
class Dialog: public QDialog
{
QSlider* brokenSlider;
public:
explicit Dialog(QWidget *parent = nullptr):QDialog(parent){
auto mainLayout = new QVBoxLayout;
brokenSlider = new QSlider(Qt::Horizontal, this);
mainLayout->addWidget(brokenSlider);
connect(brokenSlider, &QSlider::valueChanged, [&](){this->update();});
mainLayout->addWidget(new QSlider(Qt::Horizontal, this));
mainLayout->addWidget(new QSlider(Qt::Horizontal, this));
setLayout(mainLayout);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Dialog g;
g.exec();
}
I'm looking for a workaround for this Apple/QT bug.
I was able to fix the issue applying a custom stylesheet to the slider. However, doing that also creates a problem with the ticks that are not displayed.
The solution I found was to extend QSlider and paint then manually:
myslider.h:
#pragma once
#include <QStylePainter>
#include <QStyleOptionSlider>
#include <QStyleOptionComplex>
#include <QSlider>
#include <QColor>
#include "math.h"
class MySlider:public QSlider
{
public:
explicit MySlider(Qt::Orientation orientation, QWidget *parent = nullptr):QSlider(orientation, parent){};
explicit MySlider(QWidget *parent = nullptr):QSlider(parent){
this->setStyleSheet("\
QSlider::groove:horizontal {\
height: 8px; /* the groove expands to the size of the slider by default. by giving it a height, it has a fixed size */ \
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B1B1B1, stop:1 #c4c4c4);\
margin: 2px 0;\
}\
\
QSlider::handle:horizontal {\
background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f);\
border: 1px solid #5c5c5c;\
width: 18px;\
margin: -2px 0; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */ \
border-radius: 3px;\
}\
");
};
protected:
virtual void paintEvent(QPaintEvent *ev)
{
QStylePainter p(this);
QStyleOptionSlider opt;
initStyleOption(&opt);
QRect handle = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
// draw tick marks
// do this manually because they are very badly behaved with style sheets
int interval = tickInterval();
if (interval == 0)
{
interval = pageStep();
}
if (tickPosition() != NoTicks)
{
for (int i = minimum(); i <= maximum(); i += interval)
{
int x = std::round((double)((double)((double)(i - this->minimum()) / (double)(this->maximum() - this->minimum())) * (double)(this->width() - handle.width()) + (double)(handle.width() / 2.0))) - 1;
int h = 4;
p.setPen(QColor("#a5a294"));
if (tickPosition() == TicksBothSides || tickPosition() == TicksAbove)
{
int y = this->rect().top();
p.drawLine(x, y, x, y + h);
}
if (tickPosition() == TicksBothSides || tickPosition() == TicksBelow)
{
int y = this->rect().bottom();
p.drawLine(x, y, x, y - h);
}
}
}
QSlider::paintEvent(ev);
}
};
In QT Creator, if using forms, the file above needs to be added to the promoted widgets list and then each QSlider needs to be promoted to use this class.
Partial credits to: https://stackoverflow.com/a/27535264/6050364
Update (Dec 2021): QT fixed this issue in QT 6.2.3

Adding widgets and arranging them to the corner of the screen on Qt

I tried to arrange some widgets which includes two labels and one image on the top right of the screen. I am using a HBoxlayout. When I tried to add the widgets to the screen in HBoxlayout, all the widgets are not getting arranged at the position I want them to placed at. So how can I arrange these to the top of the screen.Here is the code which I tried
#include "screen.h"
#include "ui_screen.h"
#include<QGridLayout>
#include<QLabel>
#include<QBitmap>
screen::screen(QWidget *parent) :
QWidget(parent),
ui(new Ui::screen)
{
ui->setupUi(this);
QGridLayout *g=new QGridLayout();
QHBoxLayout *h=new QHBoxLayout();
QLabel *l=new QLabel();
QLabel *l2=new QLabel();
QLabel *l3=new QLabel();
QPixmap p(":/img/img/user.jpg");
l->setPixmap(p);
l->setMask(p.mask());
l->show();
l->setStyleSheet("QLabel { background-color : red; color : blue; }");
l2->setText("User");
l3->setText("Value");
h->addWidget(l,0,Qt::AlignRight);
h->addWidget(l2,0,Qt::AlignRight);
h->addWidget(l3,0,Qt::AlignRight);
g->addLayout(h,0,0,1,1,Qt::AlignTop);
this->setLayout(g);
this->showFullScreen();
}
screen::~screen()
{
delete ui;
}
The layout which I tried to make Sample layout
right now I haven't added the left sided labels yet in my code. Thanks in advance :)
Play around with the layouts like this:
#include "screen.h"
#include <QVBoxLayout>
#include <QLabel>
#include <QBitmap>
#include <QFrame>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
auto *widget = new QWidget(this);
auto *frmLogin = new QFrame(this);
auto *layoutMain = new QVBoxLayout(widget);
auto *layoutTitle = new QHBoxLayout();
auto *layoutLogin = new QHBoxLayout(frmLogin);
auto *layoutInfo = new QVBoxLayout();
auto *labTitle = new QLabel(tr("Some other stuff you want in the title"), this);
auto *labBody = new QLabel(tr("Some other stuff you want in the body"), this);
auto *labAvatar = new QLabel(this);
auto *labUser = new QLabel(tr("User"), this);
auto *labValue = new QLabel(tr("Value"), this);
QPixmap p(":/pix/images/avatars/user.png");
labTitle->setAlignment(Qt::AlignCenter);
labTitle->setStyleSheet(".QLabel { background-color: white; }");
labBody->setAlignment(Qt::AlignCenter);
labAvatar->setPixmap(p);
labAvatar->setMask(p.mask());
layoutInfo->addWidget(labUser);
layoutInfo->addWidget(labValue);
layoutLogin->addWidget(labAvatar);
layoutLogin->addLayout(layoutInfo);
frmLogin->setStyleSheet(".QFrame { background-color : orange; }");
frmLogin->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
layoutTitle->addWidget(labTitle);
layoutTitle->addWidget(frmLogin);
layoutMain->addLayout(layoutTitle);
layoutMain->addWidget(labBody);
layoutMain->setContentsMargins(0, 0, 0, 0);
layoutMain->setSpacing(0);
setCentralWidget(widget);
showFullScreen();
}
The code above results in the following layout:

QLabel changing color when mouse passed over then

sorry for the bad english, this isn't my first language.
i need help with QT.
i had to make a board, like chess board an 8 by 8 matrix of tiles.
so i made a class that inherits of QLabel named as "Casillero" and another one that is the board itself name "Tablero" that inherits of QWidget and i added it to the mainwindow as centralWidget.
the problem that i have is that when you hover the mouse over the tiles someones change color to purple and someones not. I do not understand this behavior.
the mouse is over the purple tile
the mouse is over the tile over up on the diagonal of the other picture
class tile named:"Casillero.h"
class Casillero :public QLabel
{
public:
Casillero(QWidget* pParent=0, Qt::WindowFlags f=0) : QLabel(pParent, f){};
Casillero(const QString& text, QWidget* pParent = 0, Qt::WindowFlags f = 0) : QLabel(text, pParent, f){};
void display(char elem);
void tileDisplay();
bool hayPieza;
int casilleroColor,fil,col,casilleroNum;
char piezaColor;
};
class tile named:"Casillero.cpp"
#include "casillero.h"
void Casillero::display(char elem)
{
this->piezaColor=elem;
if(this->hayPieza)
{
if(elem == 'R')
{
this->setPixmap(QPixmap(":/Imagenes/damaroja.png"));
}
else
{
this->setPixmap(QPixmap(":/Imagenes/damanegra.png"));
}
}
else
this->clear();
}
void Casillero::tileDisplay()
{
if(this->casilleroColor)
this->setStyleSheet("QLabel {background-color: rgb(118, 150, 85);}:hover{background-color: rgb(170,85,127);}");
else
this->setStyleSheet("QLabel {background-color: rgb(238, 238, 210);}:hover{background-color: rgb(170,95,127);}");
}
class board named:"Tablero.h"
class Tablero :public QWidget
{
public:
Tablero(QWidget *parent);
private:
QLabel *bordes[4];
Casillero *tab[8][8];
};
class board named:"Tablero.cbp"
#include "tablero.h"
Tablero::Tablero(QWidget *parent)
{
this->setParent(parent);
bordes[0]=new QLabel(this);
bordes[0]->setGeometry(330,105,552,20);
bordes[0]->setStyleSheet("QLabel { background-color :rgb(178, 194, 148); color : black; }");
bordes[1]=new QLabel(this);
bordes[1]->setGeometry(330,637,552,20);
bordes[1]->setStyleSheet("QLabel { background-color :rgb(178, 194, 148); color : black; }");
bordes[2]=new QLabel(this);
bordes[2]->setGeometry(330,125,20,512);
bordes[2]->setStyleSheet("QLabel { background-color :rgb(178, 194, 148); color : black; }");
bordes[3]=new QLabel(this);
bordes[3]->setGeometry(862,125,20,512);
bordes[3]->setStyleSheet("QLabel { background-color :rgb(178, 194, 148); color : black; }");
int i,j,k=0,hor,ver;
ver=125;
for(i=0;i<8;i++)
{
hor=350;
for(j=0;j<8;j++)
{
tab[i][j] = new Casillero(this);
tab[i][j]->casilleroColor=(i+j)%2;
tab[i][j]->hayPieza=false;
tab[i][j]->fil=i;
tab[i][j]->col=j;
tab[i][j]->casilleroNum=k++;
tab[i][j]->tileDisplay();
tab[i][j]->setGeometry(hor,ver,64,64);
hor+=64;
}
ver+=64;
}
//damas negras
for(j=1;j<7;j++)
{
tab[0][j]->hayPieza=true;
tab[0][j]->display('N');
tab[0][j]->piezaColor='N';
tab[7][j]->hayPieza=true;
tab[7][j]->display('N');
tab[7][j]->piezaColor='N';
}
//damas rojas
for(j=1;j<7;j++)
{
tab[j][0]->hayPieza=true;
tab[j][0]->display('R');
tab[j][0]->piezaColor='R';
tab[j][7]->hayPieza=true;
tab[j][7]->display('R');
tab[j][7]->piezaColor='R';
}
}
The problem was that there was a widget over the board in mainwindow.cpp
widget = new QWidget(centralWidget);
widget->setObjectName(QStringLiteral("widget"));
widget->setGeometry(QRect(200, 50, 551, 341));

Qt Drawing Without Erasing Components

I am using Qt 5.11.1 and Qt Creator to create a project. My code draws several ellipses in the paintEvent function that I have overriden. But because of the paintEvent function's working style the buttons that I have under the ellipses are being erased. I want to have a window that has ellipses at the top and buttons at the bottom of the window. It will roughly seem like this:
Is there any way to do this. Right now, the buttons are being erased and I only have the ellipses. I would be really glad if someone could guide me.
Thanks in advance.
Note: My ellipses are green and my background is black but I have tried by changing the background to white or changing the stylesheet of the buttons, it didn't work.
This is my .h file:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
protected:
void paintEvent(QPaintEvent *e);
void setBackGroundColorToBlack();
};
#endif // MAINWINDOW_H
This is my .cpp file:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QtGui>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setBackGroundColorToBlack();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::paintEvent(QPaintEvent *event) {
setUpdatesEnabled(false);
QPainter painterObj;
painterObj.begin(this);
painterObj.setPen(QPen(Qt::green, 2, Qt::SolidLine, Qt::RoundCap));
painterObj.drawEllipse(0, 0, 318, 390);//456
painterObj.drawEllipse(53, 65, 212, 260);//304
painterObj.drawEllipse(106, 130, 106, 130);//152
painterObj.end();
}
void MainWindow::setBackGroundColorToBlack() {
QPalette pal = palette();
// set black background
pal.setColor(QPalette::Background, Qt::black);
this->setAutoFillBackground(true); // This enables the qt to fill the background before the paint event.
this->setPalette(pal);
//update();
}
This is what I get:
My ui file is like this:
In general - don't use a QMainWindows class. It's for "big" desktop applications that have a menu, a status bar, etc. All you need is to derive from QDialog or even just QWidget. And you can paint the background yourself, so no need to mess with the pallete. The minimal example that should work is below. If the Bar button doesn't show up, then your Qt for the target is broken: the default platform style doesn't work.
// https://github.com/KubaO/stackoverflown/tree/master/questions/painted-with-children-51498155
// This project is compatible with Qt 4 and Qt 5
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
QRectF scaled(const QRectF &rect, qreal scale) {
auto const center = rect.center();
auto const w = rect.width()*scale;
auto const h = rect.height()*scale;
return {center.x() - w/2.0, center.y() - h/2.0, w, h};
}
QRectF marginAdded(const QRectF &rect, qreal margin) {
return rect.adjusted(margin, margin, -margin, -margin);
}
const char buttonQSS[] =
"* { background-color: white; border-width: 2px; border-style:solid; border-color: red;"
" border-radius: 3px; padding: 3px; }"
"*:pressed { padding-left: 5px; padding-top: 5px; background-color: lightGray; }";
class MyWindow : public QWidget {
Q_OBJECT
QPushButton m_restoreButton{"Restore Size"};
QPushButton m_otherButton{"Bar"};
QGridLayout m_layout{this};
Q_SLOT void onRestore() { resize(sizeHint()); }
public:
explicit MyWindow(QWidget *parent = {}) : QWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent);
m_layout.addItem(new QSpacerItem(0, 100, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 0, 0);
m_layout.addWidget(&m_restoreButton, 1, 0);
m_layout.addWidget(&m_otherButton, 1, 1);
m_restoreButton.setStyleSheet(buttonQSS);
connect(&m_restoreButton, SIGNAL(clicked(bool)), SLOT(onRestore()));
}
protected:
void paintEvent(QPaintEvent *) override {
qreal const penWidth = 2.0;
// Cover the area above all the children
QRectF const area = marginAdded(QRect(0, 0, width(), childrenRect().top() - 10), penWidth);
QPainter p(this);
p.fillRect(rect(), Qt::black);
p.setPen({Qt::green, penWidth});
p.drawEllipse(scaled(area, 3./3.));
p.drawEllipse(scaled(area, 2./3.));
p.drawEllipse(scaled(area, 1./3.));
}
QSize sizeHint() const override { return {320, 568}; /* iPhone 5 */ }
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyWindow w;
w.show();
return a.exec();
}
#include "main.moc"

QLineEdit with custom button

I need to implement LineEdit widget with possibility to add tool buttons at the right end of text area. I know two ways of doing that but both solutions seems ugly.
1) add tool buttons as child widgets of QLineEdit and handle resizeEvent to position them correctly. The main disadvantage is that if text is enough long it may appear under tool buttons.
2) Another solution is to put line edit and buttons inside frame and overwrite style to hide lineEdits frame and make QFrame look like QLineEdit.
I need a best way to implement such widget. Also my widget should be style aware.
As of Qt 5.2 one can use QLineEdit::addAction(...) to insert custom buttons. (Qt Docs)
Example (assume we're inside the definition of MyClass):
QLineEdit *myLineEdit = new QLineEdit(this);
QAction *myAction = myLineEdit->addAction(QIcon("test.png"), QLineEdit::TrailingPosition);
connect(myAction, &QAction::triggered, this, &MyClass::onActionTriggered);
The original blog post is gone now, but Trolltech once posted an example of a clear button for Qt 4.
Results
Line edit with no text:
Line edit with some text (button appears):
Line edit full of text (doesn’t go underneath button):
Source
lineedit.h
/****************************************************************************
**
** Copyright (c) 2007 Trolltech ASA <info#trolltech.com>
**
** Use, modification and distribution is allowed without limitation,
** warranty, liability or support of any kind.
**
****************************************************************************/
#ifndef LINEEDIT_H
#define LINEEDIT_H
#include <QLineEdit>
class QToolButton;
class LineEdit : public QLineEdit
{
Q_OBJECT
public:
LineEdit(QWidget *parent = 0);
protected:
void resizeEvent(QResizeEvent *);
private slots:
void updateCloseButton(const QString &text);
private:
QToolButton *clearButton;
};
#endif // LIENEDIT_H
lineedit.cpp
/****************************************************************************
**
** Copyright (c) 2007 Trolltech ASA <info#trolltech.com>
**
** Use, modification and distribution is allowed without limitation,
** warranty, liability or support of any kind.
**
****************************************************************************/
#include "lineedit.h"
#include <QToolButton>
#include <QStyle>
LineEdit::LineEdit(QWidget *parent)
: QLineEdit(parent)
{
clearButton = new QToolButton(this);
QPixmap pixmap("fileclose.png");
clearButton->setIcon(QIcon(pixmap));
clearButton->setIconSize(pixmap.size());
clearButton->setCursor(Qt::ArrowCursor);
clearButton->setStyleSheet("QToolButton { border: none; padding: 0px; }");
clearButton->hide();
connect(clearButton, SIGNAL(clicked()), this, SLOT(clear()));
connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateCloseButton(const QString&)));
int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
setStyleSheet(QString("QLineEdit { padding-right: %1px; } ").arg(clearButton->sizeHint().width() + frameWidth + 1));
QSize msz = minimumSizeHint();
setMinimumSize(qMax(msz.width(), clearButton->sizeHint().height() + frameWidth * 2 + 2),
qMax(msz.height(), clearButton->sizeHint().height() + frameWidth * 2 + 2));
}
void LineEdit::resizeEvent(QResizeEvent *)
{
QSize sz = clearButton->sizeHint();
int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
clearButton->move(rect().right() - frameWidth - sz.width(),
(rect().bottom() + 1 - sz.height())/2);
}
void LineEdit::updateCloseButton(const QString& text)
{
clearButton->setVisible(!text.isEmpty());
}
Once I implemented such solution for this:
// LineEdit.h
#ifndef LINEEDIT_H
#define LINEEDIT_H
#include <QLineEdit>
class QToolButton;
class LineEdit : public QLineEdit
{
Q_OBJECT
public:
LineEdit(QWidget *parent = 0);
protected:
void resizeEvent(QResizeEvent *);
private:
QToolButton *furfurIcon;
};
#endif // LINEEDIT_H
// LineEdit.cpp
#include "lineedit.h"
#include <QToolButton>
#include <QStyle>
LineEdit::LineEdit(QWidget *parent)
: QLineEdit(parent)
{
furfurIcon = new QToolButton(this);
QPixmap pixmap(":/root/your_icon");
furfurIcon->setIcon(QIcon(pixmap));
furfurIcon->setIconSize(pixmap.size());
furfurIcon->setCursor(Qt::ArrowCursor);
furfurIcon->setStyleSheet("QToolButton
"{"
"border: none; padding: 0px;"
"}");
setStyleSheet(QString("QLineEdit"
"{"
"border: 1px solid;"
"border-color: rgb(148, 168, 199);"
"border-radius: 10px;"
"background: white;"
"padding-left: %1px;"
"}").arg(furfurIcon->sizeHint().width() - 4));
setMinimumSize(0, 25);
}
void LineEdit::resizeEvent(QResizeEvent *)
{
QSize sz = furfurIcon->sizeHint();
furfurIcon->move(rect().left(), (rect().bottom() + 1 - sz.height()) / 2);
}
Position of QToolButton is handled in resizeEvent. If there are more than one you'll have to adjust their coordinates. Also you can modify it to use layout. There is no text overlapping here.