painting the widget on calling slot - c++

I want to paint a circle with user provided color and keep a line edit adjustment on it on horizontal alignment.
Used painter function call on slot, but its not working
#include <QPainter>
#include "cascadeColorHighlightWidget.h"
CascadeColorHighlightWidget::CascadeColorHighlightWidget(QWidget *parent) : QWidget(parent)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::Widget);
setAttribute( Qt::WA_DeleteOnClose, true );
setFixedSize(187,164);
setContentsMargins(0,0,0,0);
}
void CascadeColorHighlightWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
QRectF contRect = contentsRect().adjusted(1, 1, -1, -1);
painter.setPen(QPen(QColor(176, 176, 176),1));
painter.setBrush(QColor(255,255,255));
painter.drawRect(contRect);
painter.setPen(QPen(QColor(51,51,51),1));
QFont font( "Calibri" );
font.setPixelSize(14);
painter.setFont( font );
painter.drawText(QPointF(contRect.x() + 18, contRect.y() + 28), "Color Highlight");
}
void CascadeColorHighlightWidget::focusOutEvent(QFocusEvent *event)
{
Q_UNUSED(event);
close();
}
void CascadeColorHighlightWidget::setColors(QColor color)
{
QPainter painter(this);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
QRectF contRect = contentsRect().adjusted(1, 1, -1, -1);
int rectYPos = contRect.y() + 55;
painter.setPen(Qt::NoPen);
QRectF ellipseRect = QRectF(contRect.x() + 18, rectYPos, 16, 16);
painter.setPen(Qt::NoPen);
painter.setBrush(color);
painter.drawEllipse(ellipseRect);
/*After this ellipse I need to draw a line edit where user can edit anytime*/
}
But by calling setcolot its not drawing the ellipse on the widget. Only the items in paintEvent worked.
Is it possible to do with painter or I need to keep widgetItems and insert in this wideget. please give some suggestions

All painting work should happen in paintEvent. You have to keep state, and paint items accordingly. Have methods that take QPainter as an argument and call them from within paintEvent method, passing to them the QPainter object you created there.
Example:
In your widget header have:
private:
void setColors(QColor c) { color = c; }
void drawEllipse(QPainter & painter);
QColor color;
bool draw_ellipse;
As you can see, the setColors method only sets a color and you keep that color in a private instance variable color.
A new method hosts the painting job (previously in setColors):
void CascadeColorHighlightWidget::drawEllipse(QPainter &painter)
{
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
QRectF contRect = contentsRect().adjusted(1, 1, -1, -1);
int rectYPos = contRect.y() + 55;
painter.setPen(Qt::NoPen);
QRectF ellipseRect = QRectF(contRect.x() + 18, rectYPos, 16, 16);
painter.setPen(Qt::NoPen);
painter.setBrush(color);
painter.drawEllipse(ellipseRect);
/*After this ellipse I need to draw a line edit where user can edit anytime*/
}
The variable color in this line
painter.setBrush(color);
is the one you set using the setColors method.
The paintEvent method should be like:
void CascadeColorHighlightWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
QRectF contRect = contentsRect().adjusted(1, 1, -1, -1);
painter.setPen(QPen(QColor(176, 176, 176),1));
painter.setBrush(QColor(255,255,255));
painter.drawRect(contRect);
painter.setPen(QPen(QColor(51,51,51),1));
QFont font( "Calibri" );
font.setPixelSize(14);
painter.setFont( font );
painter.drawText(QPointF(contRect.x() + 18, contRect.y() + 28), "Color Highlight");
if(draw_ellipse)
{
drawEllipse(painter);
}
}
At the end of it, you test draw_ellipse (don't forget to initialize it to false in the constructor) and call the drawEllipse method if it's true.
Let's draw the ellipse, for example using QWidget's mousePressEvent:
void CascadeColorHighlightWidget::mousePressEvent(QMouseEvent *event)
{
setColors(QColor(Qt::red));
draw_ellipse = true;
update();
}
Here, you set a color, first, then set draw_ellipse to true, then (and it matters a lot) you call the update slot of QWidget:
[...] it schedules a paint event for processing when Qt returns to the
main event loop.
So the paintEvent method will be called, and your paintings updated accordingly to your class's state (color and draw_ellipse variables).

Related

How many pixels do items in QComboBox need?

I would like to know how many pixels need this items in QComboBox:
red - checkbox
green - distance between end of checkbox and beggining of the text
blue - distance between end of border and beggining of the checkbox
In doc of QStyle I find two methods:
subElementRect()
pixelMetric()
I think I have to use them, but I don't know, which args I need to use.
It depends on the style.
QCommonStyle for example draws the ComboBox's label like this:
if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
QRect editRect = proxy()->subControlRect(CC_ComboBox, cb, SC_ComboBoxEditField, widget);
p->save();
p->setClipRect(editRect);
if (!cb->currentIcon.isNull()) {
QIcon::Mode mode = cb->state & State_Enabled ? QIcon::Normal
: QIcon::Disabled;
QPixmap pixmap = cb->currentIcon.pixmap(qt_getWindow(widget), cb->iconSize, mode);
QRect iconRect(editRect);
iconRect.setWidth(cb->iconSize.width() + 4);
iconRect = alignedRect(cb->direction,
Qt::AlignLeft | Qt::AlignVCenter,
iconRect.size(), editRect);
if (cb->editable)
p->fillRect(iconRect, opt->palette.brush(QPalette::Base));
proxy()->drawItemPixmap(p, iconRect, Qt::AlignCenter, pixmap);
if (cb->direction == Qt::RightToLeft)
editRect.translate(-4 - cb->iconSize.width(), 0);
else
editRect.translate(cb->iconSize.width() + 4, 0);
}
if (!cb->currentText.isEmpty() && !cb->editable) {
proxy()->drawItemText(p, editRect.adjusted(1, 0, -1, 0),
visualAlignment(cb->direction, Qt::AlignLeft | Qt::AlignVCenter),
cb->palette, cb->state & State_Enabled, cb->currentText);
}
p->restore();
}
This means, that the actual size of the icon's rectangle can be determined by subclassing QComboBox (to access its protected initStyleOption method) and creating a new public getIconRect method in the following way:
QRect ComboBox::getIconRect()
{
QStyleOptionComboBox opt;
initStyleOption(&opt);
QRect rect(style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField));
rect.setWidth(opt.iconSize.width() + 4);
return rect;
}
Then in MainWindow for example create a ComboBox and call its getIconSize method like this:
auto *cmbBox = new ComboBox(this);
qDebug() << cmbBox->getIconRect();
For me, on Windows 10, this gives:
QRect(3,3 20x14)
There could be other ways, but if you insist on using a method similar to QStyle::subControlRect, those are the correct arguments;

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.

QT 5.7 QPainter line aligment

I am working with QT 5.7 and C++.
At the moment I try to get used to draw my own widgets with the QPainter class.
But I noticed a problem I couldn't solve.
I try to draw a border line extactly at the widget border but if I do so:
void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter;
painter.begin(this);
painter.setBrush(Qt::cyan);
QBrush brush(Qt::black);
QPen pen(brush, 2);
painter.setPen(pen);
painter.drawRect(0, 0, size().width() - 1, size().height() - 1);
painter.end();
}
The Line is at the bottom and right site bigger than the others:
And before someone is telling me I have to remove the two -1 expressions,
you should know if I do this and also set the pen width to 1 there is no line anymore at the bottom and right side.
I think this artifact is caused by the "line aligment".
QT tries to tint the the pixels near the logical lines defined by the rectangle but actually because finally all have to be in pixels it has to decide.
If I am right, why there is no method to set the line aligment of the pen like in GDI+?
And how I can solve this?
Everything depends on whether you want the entire pen's width to be visible or not. By drawing the rectangle starting at 0,0, you're only showing half of the pen's width, and that makes things unnecessarily complicated - never mind that the line appears too thin. In Qt, the non-cosmetic pen is always drawn aligned to the middle of the line. Qt doesn't let you change it: you can change the drawn geometry instead.
To get it right for odd line sizes, you must give rectangle's coordinates as floating point values, and they must be fall in the middle of the line. So, e.g. if the pen is 3.0 units wide, the rectangle's geometry will be (1.5, 1.5, width()-3.0, width()-3.0).
Here's a complete example:
// https://github.com/KubaO/stackoverflown/tree/master/questions/widget-pen-wide-38019846
#include <QtWidgets>
class Widget : public QWidget {
Q_OBJECT
Q_PROPERTY(qreal penWidth READ penWidth WRITE setPenWidth)
qreal m_penWidth = 1.0;
protected:
void paintEvent(QPaintEvent *) override {
QPainter p{this};
p.setPen({Qt::black, m_penWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin});
p.setBrush(Qt::cyan);
qreal d = m_penWidth/2.0;
p.drawRect(QRectF{d, d, width()-m_penWidth, height()-m_penWidth});
}
public:
explicit Widget(QWidget * parent = 0) : QWidget{parent} { }
qreal penWidth() const { return m_penWidth; }
void setPenWidth(qreal width) {
if (width == m_penWidth) return;
m_penWidth = width;
update();
}
QSize sizeHint() const override { return {100, 100}; }
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget top;
QVBoxLayout layout{&top};
Widget widget;
QSlider slider{Qt::Horizontal};
layout.addWidget(&widget);
layout.addWidget(&slider);
slider.setMinimum(100);
slider.setMaximum(1000);
QObject::connect(&slider, &QSlider::valueChanged, [&](int val){
widget.setPenWidth(val/100.0);
});
top.show();
return app.exec();
}
#include "main.moc"

How to draw a rectangle on a graph with XY axes Qt

I want to draw a rectangle on a graph with XY axes using Qt. I have found QCustomPlot widget, but it is not what i need (or i did not understand how to apply it to solve my problem).
Please any suggestions how to make it work?
This is an example of what you need:
#include <QWidget>
#include <QPainter>
class MyPlot : public QWidget
{
Q_OBJECT
public:
MyPlot(QWidget *parent = 0)
: QWidget(parent)
{
}
protected:
void paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.save();
painter.translate(2, height() -2); // 2 pixels between axes and the windows frame
painter.scale(1,-1);
QPen pen;
pen.setWidth(2);
painter.setPen(pen);
// X Axis
painter.drawLine(0,0, width(),0);
// Y Axis
painter.drawLine(0,0, 0,height());
pen.setWidth(4);
painter.setPen(pen);
// Rect
painter.drawRect(10,10, 60,80);
painter.restore();
}
};
You can do it by adding QCPItemRect to QCPLayer of QCustomPlot. It seems to be the easiest solution.
The simplest override paintEvent in QWidget:
void MyWidget::paintEvent(QPaintEvent * event)
{
Q_UNUSED(event);
QPainter painter(this);
painter.drawLine(0, 10, 100, 10);
painter.drawLine(10, 0, 10, 100);
painter.drawRect(20, 20, 30, 30);
}
You can use just the plain QWidget and reimplement it's paintEvent() function. The painting would be realized by the QPainter.
void CMyWidget::paintEvent(QPaintEvent* event)
{
QPainter p(this);
p.drawLine(...);
p.drawRect(...);
p.drawText(...);
}
Or you can use a QGraphicsView / QGraphicsScene framework: http://doc.qt.io/qt-4.8/graphicsview.html

setBackgroundColor does not work in QTreeWidget when I reimplement paint function

I have reimplemented paint() function, but after that when I want to change the color of topLevelItem when it expands, setBackgroundColor is not working.
How can I fix it?
I used connection; when I toggle breakpoint, the slot executes but setbackgroundColor doesn't change color.
my slot:
void Form::on_treeWidget_expanded(QTreeWidgetItem *item)
{
item->setBackgroundColor(0, (QColor::fromCmyk(15, 15, 15, 40, 255)).toRgb());
}
my paint function :
const QString txt = index.data().toString();
painter->save();
QRect rect = option.rect;
drawDisplay(painter, option, rect, txt);
rect.setX(0);
painter->restore();