how to set style sheet for custom shaped Qpushbutton - c++

I have inherited Qpushbutton to make it circular shape, if I set background in paintevent of this class it works fine but I want to set different image for each instant of this class so I tried setting it by seticon and setstylesheet but it does not work. What could be the reason. please help me out.
Thanks in advance.
this is my code.
CirclularButton::CirclularButton(QWidget *parent):QPushButton(parent)
{
}
void CirclularButton::paintEvent(QPaintEvent *e)
{
QPainter p(this);
p.setRenderHints(QPainter::Antialiasing);
p.drawEllipse(0, 0, 50, 50);
}
and in another class where I am using it,
CirclularButton *cir = new CirclularButton(this);
cir->setGeometry(50,50,50,50);
cir->setStyleSheet("border:1px solid red");

As explained in the docs here, you will need to add this into your paint event:
void paintEvent(QPaintEvent *e)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
//your other code here
}

Related

Show QWidget as focused

I've got four QLineEdit placed inside of a QLineEdits, where I want the first the parent to look as if it is in focus when any of the containing ones is selected. Note: I don't want the focus to actually change, just the "focus frame" (the thin blue border) to appear on the parent LineEdit.
I've tried to draw a rect, but while it works on Windows I'm running into issues of the drawn rectangle not looking like a proper rectangle on ex. Linux, where it is supposed to be rounded. Is there a way to fix this OR, if possible, just make it draw itself as focused despite focus not being on it?
Here's my attempt at drawing a custom rect, but haven't been able to make it successfully mirror the OS style properly.
if (childHasFocus) {
QPainter painter(this);
QLineEdit textBox;
QColor color = textBox.palette().color(QPalette::Highlight);
painter.setPen(color);
QRect rect;
rect.setTopLeft(QPoint(0,0));
rect.setWidth(this->width() - 1);
rect.setHeight(this->height() - 1);
painter.drawRect(rect);
}
EDIT: Added an image of the desired look. Note that I'm trying to get it to look like other LineEdits focusframe independent of OS, so hardcoding a blue rectangle won't work due to ex. Linux having a rounded focusframe.
Desired look:
Here's how to do it. Its a very basic class that draws the focus frame if any of the childs have focus. On focus change, we do an update (which can probably be optimized a bit to avoid unnecessary repaints).
Screenshot:
class IPEdit : public QWidget
{
public:
IPEdit(QWidget *parent = nullptr)
: QWidget(parent)
{
delete layout();
auto l = new QHBoxLayout(this);
setFocusProxy(&a);
setAttribute(Qt::WA_Hover);
for (auto *w : {&a, &b, &c, &d}) {
l->addWidget(w);
w->installEventFilter(this);
}
}
bool eventFilter(QObject *o, QEvent *e) override
{
if (e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) {
update();
}
return QWidget::eventFilter(o, e);
}
void paintEvent(QPaintEvent *e) override
{
QStyleOptionFrame opt;
opt.initFrom(this);
opt.frameShape = QFrame::StyledPanel;
opt.state |= QStyle::State_Sunken;
// clear mouseOver and focus state
// update from relevant widgets
opt.state &= ~(QStyle::State_HasFocus | QStyle::State_MouseOver);
const auto widgets = {&a, &b, &c, &d};
for (const QWidget *w : widgets) {
if (w->hasFocus()) {
opt.state |= QStyle::State_HasFocus;
}
}
opt.rect = contentsRect();
QPainter paint(this);
paint.setClipRegion(e->region());
paint.setRenderHints(QPainter::Antialiasing);
style()->drawControl(QStyle::CE_ShapedFrame, &opt, &paint, this);
}
private:
QLineEdit a;
QLineEdit b;
QLineEdit c;
QLineEdit d;
};
QlineEdit class is also a qwidget, use the setFocus method
https://doc.qt.io/qt-6/qwidget.html#setFocus

Why the application crashes when QPushButton::paintEvent is followed by QPainter::fillRect?

I am implementing a round button by subclassing QPushButton and handling the paintEvent. I want to show the text set by the user and then draw a circle.
The application crashes at QPainter::fillRect after calling the QPushButton::paintEvent method. If QPushButton::paintEvent is not called it does not crash, but the button text is not shown.
Here is my code:
class CRoundAnimatingBtn : public QPushButton
{
Q_OBJECT
public:
explicit CRoundAnimatingBtn(QWidget *parent = nullptr) : QPushButton(parent) {}
protected:
void resizeEvent(QResizeEvent *) { setMask(QRegion(rect(), QRegion::Ellipse)); }
void paintEvent(QPaintEvent * e) {
QPainter painter(this);
QPointF center(width()/2, height()/2);
QRadialGradient radialGradient(center, qMin(width(), height())/2, center);
QPushButton::paintEvent(e); // Application crashes if this is called
if (isDown()) {
radialGradient.setColorAt(0.0,Qt::transparent);
radialGradient.setColorAt(0.79, Qt::transparent);
radialGradient.setColorAt(0.80, Qt::gray);
radialGradient.setColorAt(0.95, Qt::black);
radialGradient.setColorAt(0.90, Qt::gray);
radialGradient.setColorAt(0.91, Qt::transparent);
} else {
radialGradient.setColorAt(0.0,Qt::transparent);
radialGradient.setColorAt(0.84, Qt::transparent);
radialGradient.setColorAt(0.85, Qt::gray);
radialGradient.setColorAt(0.90, Qt::black);
radialGradient.setColorAt(0.95, Qt::gray);
radialGradient.setColorAt(0.96, Qt::transparent);
}
painter.fillRect(rect(), radialGradient); // Application crashes here
}
};
How to fix the crash?
Cause
You first create a painter, passing a QPaintDevice *device to the constructor of QPainter, which calls QPainter::begin:
QPainter painter(this);
Then you call the base class implementation of
paintEvent:
QPushButton::paintEvent(e);
which creates a new painter QStylePainter p on the same paint device, before you are done with the first one:
void QPushButton::paintEvent(QPaintEvent *)
{
QStylePainter p(this);
QStyleOptionButton option;
initStyleOption(&option);
p.drawControl(QStyle::CE_PushButton, option);
}
Finally, you try to draw with the first painter QPainter painter using:
painter.fillRect(rectangle, radialGradient);
Important: Such approach is not allowed, as the documentation of QPainter::begin clearly says:
Warning: A paint device can only be painted by one painter at a time.
Solution
Having this in mind, I would suggest you to avoid having two active painters at the same time by moving QPushButton::paintEvent(e); to the very beginning of CRoundAnimatingBtn::paintEvent (before everything else in this event handler).
Note: If you put QPushButton::paintEvent(e); at the very end of CRoundAnimatingBtn::paintEvent, the default implementation will overpaint your custom drawing and it would not be visible.
Example
Here is how the CRoundAnimatingBtn::paintEvent might look like:
void paintEvent(QPaintEvent * e) {
QPushButton::paintEvent(e);
QPainter painter(this);
QPointF center(width()/2, height()/2);
QRadialGradient radialGradient(center, qMin(width(), height())/2, center);
...
painter.fillRect(rect(), radialGradient);
}
The example produces the following result:
As you see, the text is shown together with your custom drawing.

Error with drawing point on QLabel

I'm trying to draw on QLabel in Qt like this:
paintscene.h:
class PaintScene : public QWidget
{
Q_OBJECT
public:
PaintScene(QWidget* parent = NULL);
QVector<QLabel*> _layers;
QColor _color;
int _width;
void mousePressEvent(QMouseEvent* event);
private slots:
void updateWidth();
};
paintscene.cpp:
PaintScene::PaintScene(QWidget* parent) : QWidget(parent)
{
_width = 10;
_color = Qt::red;
QLabel* inital = new QLabel(this);
inital->setStyleSheet("QLabel { background-color : white; }");
_layers.push_back(inital);
QGridLayout* layout = new QGridLayout();
layout->addWidget(inital, 1, 1, 1, 1);
this->setLayout(layout);
}
void PaintScene::mousePressEvent(QMouseEvent *event)
{
QImage tmp = _layers.back()->pixmap()->toImage();
QPainter painter(&tmp);
QPen paintpen(_color);
paintpen.setWidth(_width);
painter.setPen(paintpen);
painter.drawPoint(event->x(), event->y());
_layers.back()->setPixmap(QPixmap::fromImage(tmp));
}
The list is needed because I want to implement the work with layers (QLabel - a separate layer).
However, I get an error, the program terminates. The error occurs on the line QImage tmp = _layers.back()->pixmap()->toImage();.
What makes this happen? How can this be fixed? Maybe for a layer to use something different, not QLabel?
#Jeremy Friesner is right about the reason for the error, not having a QPixmap this will be null, in my answer I will show a possible solution
void PaintScene::mousePressEvent(QMouseEvent *event)
{
QLabel *label = _layers.back();
const QPixmap *pix= label->pixmap();
QPixmap pixmap;
if(pix)
pixmap = *pix;
else{
pixmap = QPixmap(label->size());
pixmap.fill(Qt::transparent);
}
QPainter painter(&pixmap);
QPen paintpen(_color);
paintpen.setWidth(_width);
painter.setPen(paintpen);
painter.drawPoint(event->pos());
painter.end();
label->setPixmap(pixmap);
}
From the Qt docs for QLabel::pixmap():
This property holds the label's pixmap
If no pixmap has been set this will return 0.
... so when you do this:
QImage tmp = _layers.back()->pixmap()->toImage();
pixmap() is returning NULL (because the QLabel has never had any QPixmap set on it yet), and then you try to dereference that NULL pointer to call toImage() on it, hence the crash.
To avoid crashing, don't try to create a QImage from a NULL QPixmap pointer.
I suspect you wanted to be calling grab() instead of pixmap() -- grab() will create a QPixmap for you that contains the visual appearance of the QLabel. However, an even better approach would be to avoid messing about with QPixmaps at all; instead, make your own subclass of the QLabel class, and override its paintEvent(QPaintEvent *) method to first call up to QLabel::paintEvent(e) and then use a QPainter to draw the additional point afterwards. That will be easier to implement and also more efficient at runtime.

Qt how to create bitmap from data from QVector and show it on widget?

I'm wondering how I can create bitmap from my data and show it on my widget.
I have QVector vector, which is vector of some points to draw chart. How I can repaint it on my widget but with using QBitmap? I don't want draw simply on widget, I prefer pass the pixmap to widget, just to show it.
How can I do this?
My code:
QPainter painter(pixMap);
painter.setPen(QPen(Qt::black, 2));
painter.drawPolyline(this->data.data(), this->data.size());
painter.drawLine(QPointF(5,5),QPointF(50,50));
setPixmap(*pixMap);
Here is my sample code. Why it's not working? I can't see anything on widget.
I have widget class
class Widget : public QLabel
{
public:
Widget(QVector<QPointF> * data);
~Widget();
protected:
void paintEvent(QPaintEvent * event);
private:
QVector<QPointF> data;
QPixmap *pixMap;
};
In constructor I have
Widget::Widget(QVector<QPointF> * data){
pixMap = new QPixmap(300,300);
pixMap->fill(Qt::red);
}
And in paintEvent
void Waveform::paintEvent(QPaintEvent *event)
{
QPainter painter(pixMap);
painter.setPen(QPen(Qt::white, 2));
painter.drawPolyline(this->data.data(), this->data.size());
painter.drawLine(QPointF(5,5),QPointF(50,50));
setPixmap(*pixMap);
}
If I replace QPainter painter(pixMap) with QPainter painter(this), I can see my chart. But I want to use pixmap.
I think so, but I wasn't sure without full code, now I am absolutely sure. You should do standard processing of paintEvent. So try this:
void Waveform::paintEvent(QPaintEvent *e)
{
static const QPointF points[3] = {
QPointF(10.0, 80.0),
QPointF(20.0, 10.0),
QPointF(80.0, 30.0),
};
QPainter painter(pixMap);
painter.setPen(QPen(Qt::black, 2));
painter.drawPolyline(points, 3);
painter.drawLine(QPointF(5,5),QPointF(50,50));
setPixmap(*pixMap);
QLabel::paintEvent(e);//standard processing
}
But I think that you don't need paintEvent at all, then you can totally remove paintEvent from your class or do
void VertLabel::paintEvent(QPaintEvent *e)
{
QLabel::paintEvent(e);//in this case you don't need paintEvent at all, remove it from cpp and header files
}
and in constructor:
pixMap = new QPixmap(300,300);
pixMap->fill(Qt::red);
this->resize(300,300);
static const QPointF points[3] = {
QPointF(10.0, 80.0),
QPointF(20.0, 10.0),
QPointF(80.0, 30.0),
};
QPainter painter(pixMap);
painter.setPen(QPen(Qt::black, 2));
painter.drawPolyline(points, 3);
painter.drawLine(QPointF(5,5),QPointF(50,50));
setPixmap(*pixMap);

Qt Display button on top of image

I need to display a button on top of an image. Something similar to
The background is a QPixmap/QImage and the button is a QPushbutton. I need to be able to dynamically change the image - so I am not sure if a stylesheet would be suitable for the task. I tried this, but could not get it to work.
Any solutions?
Subclass QWidget and implement paintEent where you can paint your image at the background. Set and change background image by stylesheet also possible.
Add layout with button to this widget.
There are something like this:
class WidgetWithButton
: public QWidget
{
Q_OBJECT
QImage m_bgImage;
public:
WidgetWithButton(QWidget* aParent)
: QWidget(aParent)
{
QHBoxLayout* l = new QHBoxLayout(this);
QPushButton* myButton = new QPushButton(tr("Close"));
l->addWidget( myButton, 0, Qt::AlignCenter );
}
void setImage(const QImage& aImage)
{
m_image = aImage;
update();
}
protected:
virtual void paintEvent(QPaintEvent* aPainEvent)
{
if (m_image.isValid())
{
QPainter painter(this);
painter.drawImage(rect(), m_image);
}
else
QWidget::paintEvent(aPainEvent);
}
};