Paint QPushButton with QLinearGradient - c++

I am trying to use a QLinearGradient to paint a QPushButton with no success. I have found examples on how to paint it with a solid color. But I have been unsuccessful in finding examples for a color gradient. Moreover, my approach did not work.
Here is my complete example where the solid color push button works and the linear gradient one does not:
#include <QApplication>
#include <QGridLayout>
#include <QLinearGradient>
#include <QPalette>
#include <QPushButton>
int main(int argc, char** argv)
{
QApplication app(argc, argv);
// Create layout
QGridLayout* layout = new QGridLayout;
// Create first button
QPushButton* button_1 = new QPushButton();
layout->addWidget(button_1, 0, 0);
QPalette palette_1 = button_1->palette();
palette_1.setColor(QPalette::Button, Qt::red);
button_1->setPalette(palette_1);
button_1->update();
// Create second button
QPushButton* button_2 = new QPushButton();
layout->addWidget(button_2, 0, 1);
QLinearGradient gradient_button(0, 0, button_2->width(), 0);
gradient_button.setColorAt(0, Qt::white);
gradient_button.setColorAt(1, Qt::black);
QPalette palette_2 = button_2->palette();
QBrush brush(gradient_button);
palette_2.setBrush(QPalette::Button, brush);
button_2->setPalette(palette_2);
button_2->update();
// Create widget
QWidget* widget = new QWidget;
widget->setLayout(layout);
widget->resize(300, 50);
/// Show
widget->show();
// Run
return app.exec();
}
Any ideas on what am I doing wrong?

Without a success I've tried it with QPalette and done it successfully using setStyleSheet:
QPushButton* button = new QPushButton();
QString linearGradient = QString("qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 rgba(0, 0, 0, 255), stop:1 rgba(255, 255, 255, 255));");
button->setStyleSheet(QString("background-color: %1").arg(linearGradient));
Also, we can use QString::arg(...) to set different colors and points for the gradient.
Hope this help you and excuse me for a stupid comment earlier )

Your gradient is configured to go from <0, 0> to <button_2->width(), 0>, but at the moment you create your gradient button_2 is not included in any layout: its width will be computed when the parent widget (therefore the layout where the button is) is resized. If you try fixing the width you'll see the gradient works as expected.
QPushButton* button_2 = new QPushButton();
button_2->setFixedWidth(100);
You can use an event filter to watch for the resize and adjust the gradient accordingly:
class ButtonResizeWatcher : public QObject {
protected:
virtual bool eventFilter(QObject* o, QEvent* e) override {
if (e->type() == QEvent::Resize) {
auto button = qobject_cast<QPushButton*>(o);
QLinearGradient gradient_button(0, 0, button->width(), 0);
gradient_button.setColorAt(0, Qt::white);
gradient_button.setColorAt(1, Qt::red);
auto palette = button->palette();
palette.setBrush(QPalette::Button, QBrush(gradient_button));
button->setPalette(palette);
}
return QObject::eventFilter(o, e);
}
};
Use:
ButtonResizeWatcher resize_watcher;
button_2->installEventFilter(&resize_watcher);
Full code can be found in GitHub.
Another option, as commented in a different answer, would be to use the stylesheet (qlineargradient). It depends on you if you need further control on the brush, such as "show gradient but only until certain width is reached". Also, take into consideration that stylesheets usually conflicts with other QStyles (if used).

Related

How to fix display problem when I use QGraphicsView and QGraphicsEffect together in Qt?

I have a problem when use QGraphicsView and QGraphicsBlurEffect in my project. When I put them together, my program does not work normally. I wrote a tiny program to reproduce this problem.
The Widget class is inherited from QGraphicsView.
Widget::Widget(QWidget *parent)
: QGraphicsView(parent)
{
scene = new QGraphicsScene(this);
this->setScene(scene);
label = new QLabel;
QPixmap pixmap = QPixmap("../partly_cloudy.png").scaledToWidth(200);
label->setPixmap(pixmap);
label->setGeometry(100,100, pixmap.width(), pixmap.height());
label->setStyleSheet("border:3px;border-color: rgb(255, 100, 0); border-style:solid;");
/* ********image won't show when adding following comment code********
QGraphicsBlurEffect *blur = new QGraphicsBlurEffect;
blur->setBlurRadius(10);
label->setGraphicsEffect(blur);
******* */
QGraphicsProxyWidget *proxyWidget = new QGraphicsProxyWidget;
proxyWidget->setWidget(label);
proxyWidget->setPos(10,10);
scene->addItem(proxyWidget);
}
and main.cpp is as follows.
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
This is a screenshot when QGraphicsBlurEffect is not used.
However, this is a screenshot when QLabel uses setGraphicsEffect() to bind blur effect.
To solve this problem, I tried to use a QWidget to wrap QLabel. When I did this, QLabel was rendered. However, it seems to be bounded by a rectangle area.
Widget::Widget(QWidget *parent)
: QGraphicsView(parent)
{
scene = new QGraphicsScene(this);
this->setScene(scene);
/* ********/
container = new QWidget;
container->setStyleSheet("border:3px;border-color: blue; border-style:solid;");
/******** */
label = new QLabel(container);
QPixmap pixmap = QPixmap("../partly_cloudy.png").scaledToWidth(200);
label->setPixmap(pixmap);
label->setGeometry(100,100, pixmap.width(), pixmap.height());
label->setStyleSheet("border:3px;border-color: red; border-style:solid;");
QGraphicsBlurEffect *blur = new QGraphicsBlurEffect;
blur->setBlurRadius(10);
label->setGraphicsEffect(blur);
QGraphicsProxyWidget *proxyWidget = new QGraphicsProxyWidget;
proxyWidget->setWidget(container);
proxyWidget->setPos(80,80);
qDebug() << proxyWidget->boundingRect();
scene->addItem(proxyWidget);
this->setSceneRect(0,0,640,480);
}
The screenshot of the result is.
I tried to set proxyWidget position to {0,0}, and it works normally. it seems that the position of effect rectangle will not influenced by proxyWidget position.
By the way, the version of Qt is 5.14.2.
I've searched for a long time on net. But no use. Please help or try to give some ideas how to achieve this.

QWidget's background applied to all its QWidget children

I chose to use Qt to manage the GUI of a project I am working on.
After finding how to apply a picture at the bottom of my QWidget, I noticed that it has an impact on all the components that are added to it.
Whatever the style applied through the setStyleSheet method or even with a QPixmap, the background of these elements is always the image defined for the QWidget container.
How can I avoid this behavior ?
Here is my code :
MainMenu::MainMenu(QWidget* Parent, const QPoint& Position, const QSize& Size) : QWidget(Parent) {
QString qwidgetStyle = "QWidget {background-image: url(background.jpg); border: 5px solid rgba(3, 5, 28, 1);}";
QString buttonStyle = "color: rgba(73, 123, 176, 1); font-size:30px; background-color: rgba(73, 123, 176, 1);";
move(Position);
resize(Size);
setStyleSheet(qwidgetStyle);
// Menu title
QLabel *title = new QLabel(this);
title->setText("Menu");
title->setStyleSheet(buttonStyle);
title->setAlignment(Qt::AlignCenter);
// Menu buttons
// Play
buttonPlay = new QPushButton("Play");
(*buttonPlay).setEnabled(true);
(*buttonPlay).setStyleSheet(buttonStyle);
connect(buttonPlay, SIGNAL(clicked()), this, SLOT(handleButton()));
// Option
buttonOptions = new QPushButton("Options", this);
(*buttonOptions).setEnabled(true);
(*buttonOptions).setGeometry(250, 175, 100, 50);
(*buttonOptions).setStyleSheet(buttonStyle);
connect(buttonOptions, SIGNAL(clicked()), this, SLOT(handleButton()));
// Quit
buttonQuit = new QPushButton("Quit", this);
(*buttonQuit).setEnabled(true);
(*buttonQuit).setGeometry(250, 275, 100, 50);
(*buttonQuit).setStyleSheet(buttonStyle);
connect(buttonQuit, SIGNAL(clicked()), this, SLOT(handleButton()));
// Layout
QGridLayout *layout = new QGridLayout;
layout->setMargin(50);
layout->addWidget(title, 0, 0, 1, 5);
layout->addWidget(buttonPlay, 3, 1, 2, 3);
layout->addWidget(buttonOptions, 4, 1, 2, 3);
layout->addWidget(buttonQuit, 5, 1, 2, 3);
setLayout(layout);
show();
}
The behavior you encountered is perfectly normal, because of the following lines :
QString qwidgetStyle = "QWidget {background-image: url(background.jpg); border: 5px solid rgba(3, 5, 28, 1);}";
...
setStyleSheet(qwidgetStyle);
Here, you just told Qt to apply qwidgetstyle to every QWidget of your application, with the keyword QWidget. That's why in Qt, you better set a name to your object if you want to apply a style to this particular object.
In your code, QLabel and QPushButton both inherit from QWidget, so they will have the style you defined for a QWidget, unless you name them or you specify the style for each one.
If you want to set style sheet for your MainMenu which inherits directly from QWidget (which is what you are doing in the first place), you have to set a name, and then apply the style :
setObjectName("MainMenu");
QString qwidgetStyle = "QWidget#MainMenu {background-image: url(background.jpg); border: 5px solid rgba(3, 5, 28, 1);}";
setStyleSheet(qwidgetStyle); // here, only your MainMenu will have the style "qwidgetstyle"
Notice that you can, for example, set the same style sheet for every QWidget, and only add a particular color for your MainMenu :
// this is in a CSS, but you can apply it directly from the MainMenu constructor of course
QWidget, QWidget#MainMenu {
background-image: url(background.jpg);
border: 5px solid rgba(3, 5, 28, 1);
} // aplied to all QWidget
QWidget#MainMenu {
color : #9b9b9b; // a nice grey, only applied to MainMenu
}
Again, be specific when using style sheets or you will end up having strange colors/alignments everywhere in your application :). Hope that helps!
NB : you can also thank #PaulRooney who gave a very good link in the comments.

QPushButton does not honor horizontal expanding size policy

I'm trying to put several QPushButton entities inside a QVBoxLayout such that they are centered and expanding. The expanding tag works fine until I tell the QVBoxLayout to use AlignHCenter, after which the QPushButton's all jump to the minimum size and stay there. What am I doing wrong?
QVBoxLayout *vBoxLayout = new QVBoxLayout(this);
setLayout(vBoxLayout);
//Create title and add to layout
QLabel *titleLabel = new QLabel(this);
titleLabel->setText(menuTitle);
titleLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
titleLabel->setMaximumHeight(35);
titleLabel->setStyleSheet(QString("QLabel { font-size: 16pt; }"));
vBoxLayout->addWidget(titleLabel);
vBoxLayout->setStretchFactor(titleLabel, 1);
//Create buttons and add to layout
QMap<int, QString>::const_iterator it;
for (it = m_buttonMapping.cbegin(); it != m_buttonMapping.cend(); ++it)
{
QPushButton *button = new QPushButton(it.value(), this);
connect(button, SIGNAL(clicked()), sigMapper, SLOT(map()));
sigMapper->setMapping(button, it.key());
button->setMinimumHeight(40);
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
button->setMaximumWidth(800);
button->setMinimumWidth(300);
vBoxLayout->addWidget(button);
vBoxLayout->setAlignment(button, Qt::AlignHCenter); //<-- without this, expanding works fine!
vBoxLayout->setStretchFactor(button, 1);
}
vBoxLayout->setContentsMargins(10, 0, 10, 0);
By specifying the alignment on the layout, you keep your QPushButtons from being able to expand. Available new space will be used to keep the QPushButtons centered, instead of allowing them to resize and for an amount of space around them to be utilized for centering. Stretch factors fulfill your requirement for a proportional resizing and centering of a layout's contents.
To get around this, create a wrapper widget and layout (or just a layout), and add the widget that is laid out by your vBoxLayout to the wrapper layout with a stretch factor applied. Before and after adding your widget, you'll add QSpacerItems to the wrapper layout with QHBoxLayout::addStretch. You can then adjust the stretch factors of your widget and the spacers to get the effect you want.
Here's some sample code that should solve your problem:
MainWindow.cpp
#include "MainWindow.hpp"
#include <QPushButton>
#include <QLabel>
#include <QBoxLayout>
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent) {
QWidget* centralWidget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(centralWidget);
// Create a wrapper widget that will align horizontally
QWidget* alignHorizontalWrapper = new QWidget(centralWidget);
layout->addWidget(alignHorizontalWrapper);
// Layout for wrapper widget
QHBoxLayout* wrapperLayout = new QHBoxLayout(alignHorizontalWrapper);
// Set its contents margins to 0 so it won't interfere with your layout
wrapperLayout->setContentsMargins(0, 0, 0, 0);
wrapperLayout->addStretch(1);
QWidget* widget = new QWidget(alignHorizontalWrapper);
wrapperLayout->addWidget(widget, 3);
wrapperLayout->addStretch(1);
QVBoxLayout* vBoxLayout = new QVBoxLayout(widget);
QLabel* titleLabel = new QLabel(this);
titleLabel->setText(QStringLiteral("Menu"));
titleLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
titleLabel->setMaximumHeight(35);
titleLabel->setStyleSheet(QStringLiteral("QLabel { font-size: 16pt; }"));
vBoxLayout->addWidget(titleLabel);
vBoxLayout->setStretchFactor(titleLabel, 1);
for (int i = 0; i < 3; ++i) {
const QString& value = QStringLiteral("Button ") + QString::number(i);
QPushButton* button = new QPushButton(value, this);
button->setMinimumHeight(40);
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
//button->setMaximumWidth(800);
button->setMinimumWidth(300);
vBoxLayout->addWidget(button);
//vBoxLayout->setAlignment(button, Qt::AlignHCenter); // without this, expanding works fine!
vBoxLayout->setStretchFactor(button, 3);
}
vBoxLayout->setContentsMargins(10, 0, 10, 0);
this->setCentralWidget(centralWidget);
}
MainWindow.hpp
#ifndef MAINWINDOW_HPP
#define MAINWINDOW_HPP
#include <QMainWindow>
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr);
};
#endif // MAINWINDOW_HPP
main.cpp
#include "MainWindow.hpp"
#include <QApplication>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}

QResizeEvent causes SIGSEGV

My purpose is to use a class called overlay.h to add a rectangular box and text on top of a Widget (MarbleWidget). Here below is my code for the GUI. I tried to remove all unnecessary parts:
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setupUi(this);
}
MainWindow::~MainWindow()
{
}
void MainWindow::resizeEvent(QResizeEvent *event)
{
overlay->resize(event->size()); //////////////// CAUSES SIGSEGV!!!!!!!!!!!!!
event->accept();
}
void MainWindow::setupUi(QMainWindow *MainWindow)
{
MainWindow->showMaximized();
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
sizePolicy.setHorizontalStretch(0);
sizePolicy.setVerticalStretch(0);
sizePolicy.setHeightForWidth(MainWindow->sizePolicy().hasHeightForWidth());
MainWindow->setSizePolicy(sizePolicy);
MainWindow->setTabShape(QTabWidget::Rounded);
QWidget *centralwidget = new QWidget(MainWindow);
centralwidget->setObjectName(QString::fromUtf8("centralwidget"));
sizePolicy.setHeightForWidth(centralwidget->sizePolicy().hasHeightForWidth());
centralwidget->setSizePolicy(sizePolicy);
centralwidget->setLayoutDirection(Qt::LeftToRight);
QGridLayout *gridLayout_4 = new QGridLayout(centralwidget);
gridLayout_4->setObjectName(QString::fromUtf8("gridLayout_4"));
QSplitter *splitter_4 = new QSplitter(centralwidget);
splitter_4->setObjectName(QString::fromUtf8("splitter_4"));
splitter_4->setOrientation(Qt::Horizontal);
QTabWidget *tabWidget_2 = new QTabWidget(splitter_4);
tabWidget_2->setObjectName(QString::fromUtf8("tabWidget_2"));
sizePolicy.setHeightForWidth(tabWidget_2->sizePolicy().hasHeightForWidth());
tabWidget_2->setSizePolicy(sizePolicy);
tabWidget_2->setMaximumSize(QSize(443, 16777));
tabWidget_2->setAutoFillBackground(true);
tabWidget_2->setTabPosition(QTabWidget::West);
tabWidget_2->setTabShape(QTabWidget::Rounded);
tabWidget_2->setIconSize(QSize(30, 16));
QWidget *tab_10 = new QWidget();
tab_10->setObjectName(QString::fromUtf8("tab_10"));
QGridLayout *gridLayout = new QGridLayout(tab_10);
gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
QVBoxLayout *verticalLayout_4 = new QVBoxLayout();
verticalLayout_4->setObjectName(QString::fromUtf8("verticalLayout_4"));
QHBoxLayout *horizontalLayout_7 = new QHBoxLayout();
horizontalLayout_7->setObjectName(QString::fromUtf8("horizontalLayout_7"));
QLabel *label_3 = new QLabel(tab_10);
label_3->setObjectName(QString::fromUtf8("label_3"));
horizontalLayout_7->addWidget(label_3);
verticalLayout_4->addLayout(horizontalLayout_7);
QTreeView *treeView_4 = new QTreeView(tab_10);
treeView_4->setObjectName(QString::fromUtf8("treeView_4"));
QStandardItemModel *standardModel = new QStandardItemModel ;
QStandardItem *rootNode = standardModel->invisibleRootItem();
treeView_4->setModel(standardModel);
verticalLayout_4->addWidget(treeView_4);
gridLayout->addLayout(verticalLayout_4, 0, 0, 1, 1);
tabWidget_2->addTab(tab_10, QString());
treeView_4->raise();
Marble::MarbleWidget* MarbleWidget = new Marble::MarbleWidget(splitter_4);
splitter_4->addWidget(MarbleWidget);
splitter_4->setStretchFactor(0,0);
splitter_4->setStretchFactor(1,6);
gridLayout_4->addWidget(splitter_4, 0, 0, 1, 1);
MainWindow->setCentralWidget(centralwidget);
MarbleWidget->raise();
tabWidget_2->raise();
overlay = new Overlay(MarbleWidget);
overlay->raise();
QMetaObject::connectSlotsByName(MainWindow);
} // setupUi
overlay.h
#include <QWidget>
#include <QPainter>
class Overlay : public QWidget
{
public:
Overlay(QWidget *parent);
protected:
void paintEvent(QPaintEvent *event);
};
overlay.cpp
#include "overlay.h"
Overlay::Overlay(QWidget *parent)
: QWidget(parent)
{
setPalette(Qt::transparent);
setAttribute(Qt::WA_TransparentForMouseEvents);
}
void Overlay::paintEvent(QPaintEvent *event)
{
QFont font;
font.setStyleHint(QFont::Helvetica, QFont::PreferAntialias);
font.setPointSize(10);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QColor(10, 10, 10, 255));
painter.fillRect(QRect(50, 50, 100, 100), QColor(100, 100, 100, 120));
painter.setFont(font);
painter.drawText(20, 20, "hi..............................");
}
The problem is that overlay->resize(event->size()); causes SIGSEGV when the core runs that line.
What is wrong with the code, how can I fix it?
The problem is solved by Thiago from freenode.
He pointed that MainWindow->showMaximized(); causes resize() event to be occurred before overlay is initialized. Removing that line or moving it to after the initialization solves the problem.
Take a look at official documentation: QWidget, Qt5 at resize method. Here you can read
Warning: Calling resize() or setGeometry() inside resizeEvent() can lead to infinite recursion.
You are doing exactly the same. To avoid infinite recursion and respectively your SIGSEGV you can (descending order of difficulty, descending order of true-way):
overwrite your resizeEvent() in Overlay and comment that line in MainWindow's resizeEvent;
in MainWindow's resizeEvent emit signal like mainWindowSizeChanged(QSize) and connect it to your Overlay's slot;
do QTimer::singleShot(...) in MainWindow's resizeEvent, so it will call a given slot after a given time interval.

QGraphicsView possible bug?

The example code is from my project. I've tried to make it as short as possible and to the point.
The overlay is used to draw over all the other widgets in the app. This works for most widgets, but today I've started to notice that QAbstractScrollArea subclasses are giving me a hard time. The problem is that the overlay appears not on top, and whatever drawing that happens is blocked.
#include <QtGui/QApplication>
#include <QtGui/QVBoxLayout>
#include <QtGui/QGraphicsView>
#include <QtGui/QPushButton>
class View : public QGraphicsView{
public:
View(){
//delete viewport(); setViewport(new QWidget);
}
};
class Widget : public QWidget{
QWidget* overlay_;
public:
Widget(){
resize(512, 512);
QVBoxLayout* layout = new QVBoxLayout;
QPushButton* button = new QPushButton(" Click Me! ");
layout->addWidget(button);
layout->addWidget(new View);
overlay_ = new QWidget(this);
overlay_->installEventFilter(this);
connect(button, SIGNAL(clicked()),
overlay_, SLOT(show()));
overlay_->hide();
setLayout(layout);
}
bool eventFilter(QObject* target, QEvent* event){
if(target == overlay_){
if(event->type() == QEvent::Paint && overlay_->isVisible()){
overlay_->resize(size());
QPainter painter(overlay_);
painter.setPen(QPen(QColor(1, 102, 192, 255), 1, Qt::SolidLine,
Qt::FlatCap, Qt::MiterJoin));
painter.drawRect(rect().adjusted(60, 0, -60, 0));
return true;
}
}
}
};
int main(int argc, char *argv[]){
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
To fix this in this example and have overlay go on top of View, you'll need to uncomment the commented line at the top. So my question is this: why do I need to delete and assign a new viewport widget in the constructor in order for overlay not get overdrawn?
This isn't a bug with QGraphicsView, it will happen if you use a standard QScrollArea as well.
The issue, I think, is the order in which Qt draws child widgets. Sibling widgets are drawn in the order they are added to the parent (although you can't rely on this).
The reason that resetting the viewport "solved" the problem is because when you do that you create a new QWidget that has no background to be the viewport. The QGraphicsView is still being drawn over the overlay_, it just has a transparent viewport. Notice how it's still drawn behind the pushbutton, however.
If you want to draw an overlay only over the QGraphicsView, you can override QGraphicsView::paintEvent() and do it there. If you want to draw the overlay over your entire widget, I would embed your layout inside a second QWidget and then try using QWidget::raise() to force the overlay visually to the top.