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
Related
I'm new on Qt and c++, so I'm having some difficulties. I'm trying to create a widget that can get the mouseMoveEvent position and draw an ellipse on my pixmap on mouse position. Below you can see the code:
#include "myimage.h"
#include <QPainter>
#include <QPen>
#include <QColor>
#include <QMouseEvent>
#include <QDebug>
Myimage::Myimage(QWidget *parent) : QWidget(parent)
{
setMouseTracking(true); // E.g. set in your constructor of your widget.
}
// Implement in your widget
void Myimage::mouseMoveEvent(QMouseEvent *event)
{
qDebug() << event->pos();
}
void Myimage::paintEvent(QPaintEvent * event)
{
event->accept();
QPixmap pixmap2("/home/gabriel/Qt_interfaces/OpenCVTests/Webcam_PyQt5/Images/Court_top_View.jpg");
QRect rectangle(0, 0, width()-1, height()-1);
QPainter painter(this);
painter.drawRect(rectangle);
painter.drawPixmap(5, 5, width()-10, height()-10, pixmap2);
painter.drawEllipse(pos(), 10 ,10 );
}
The mouse position is being printed on console, but no ellipse on image.
Could you help me?
Regards,
Gabriel.
According to the doc:
pos : QPoint
This property holds the position of the widget within its parent
widget.
If the widget is a window, the position is that of the widget on the
desktop, including its frame.
...
Access functions:
QPoint pos() const void
move(int x, int y)
void move(const QPoint &)
As we see this data we do not want it, a possible solution is to create a variable that stores the value of the position obtaining through QMouseEvent and update the painting through the function update(), in addition the first time the Widget there should be no ellipse so we check that the position has been assigned through the function isNull() of QPoint, as I show below:
*.h
private:
QPoint mPoint;
*.cpp
Myimage::Myimage(QWidget *parent)
: QWidget(parent)
{
setMouseTracking(true);
}
void Myimage::mouseMoveEvent(QMouseEvent *event)
{
mPoint = event->pos();
update();
}
void Myimage::paintEvent(QPaintEvent *)
{
QPixmap pixmap2("/home/gabriel/Qt_interfaces/OpenCVTests/Webcam_PyQt5/Images/Court_top_View.jpg");
QRect rectangle(0, 0, width()-1, height()-1);
QPainter painter(this);
painter.drawRect(rectangle);
painter.drawPixmap(5, 5, width()-10, height()-10, pixmap2);
if(!mPoint.isNull()){
painter.drawEllipse(mPoint, 10 ,10 );
}
}
I'm new on Qt and c++, so I'm having some difficulties. I'm trying to create a widget that can get the mouseMoveEvent position and draw an ellipse on my pixmap on mouse position. Below you can see the code:
#include "myimage.h"
#include <QPainter>
#include <QPen>
#include <QColor>
#include <QMouseEvent>
#include <QDebug>
Myimage::Myimage(QWidget *parent) : QWidget(parent)
{
setMouseTracking(true); // E.g. set in your constructor of your widget.
}
// Implement in your widget
void Myimage::mouseMoveEvent(QMouseEvent *event)
{
qDebug() << event->pos();
}
void Myimage::paintEvent(QPaintEvent * event)
{
event->accept();
QPixmap pixmap2("/home/gabriel/Qt_interfaces/OpenCVTests/Webcam_PyQt5/Images/Court_top_View.jpg");
QRect rectangle(0, 0, width()-1, height()-1);
QPainter painter(this);
painter.drawRect(rectangle);
painter.drawPixmap(5, 5, width()-10, height()-10, pixmap2);
painter.drawEllipse(pos(), 10 ,10 );
}
The mouse position is being printed on console, but no ellipse on image.
Could you help me?
Regards,
Gabriel.
According to the doc:
pos : QPoint
This property holds the position of the widget within its parent
widget.
If the widget is a window, the position is that of the widget on the
desktop, including its frame.
...
Access functions:
QPoint pos() const void
move(int x, int y)
void move(const QPoint &)
As we see this data we do not want it, a possible solution is to create a variable that stores the value of the position obtaining through QMouseEvent and update the painting through the function update(), in addition the first time the Widget there should be no ellipse so we check that the position has been assigned through the function isNull() of QPoint, as I show below:
*.h
private:
QPoint mPoint;
*.cpp
Myimage::Myimage(QWidget *parent)
: QWidget(parent)
{
setMouseTracking(true);
}
void Myimage::mouseMoveEvent(QMouseEvent *event)
{
mPoint = event->pos();
update();
}
void Myimage::paintEvent(QPaintEvent *)
{
QPixmap pixmap2("/home/gabriel/Qt_interfaces/OpenCVTests/Webcam_PyQt5/Images/Court_top_View.jpg");
QRect rectangle(0, 0, width()-1, height()-1);
QPainter painter(this);
painter.drawRect(rectangle);
painter.drawPixmap(5, 5, width()-10, height()-10, pixmap2);
if(!mPoint.isNull()){
painter.drawEllipse(mPoint, 10 ,10 );
}
}
I have written application that draws two smiling faces:
First one is painted directly on QWidget:
void DirectFace::paintEvent(QPaintEvent *ev)
{
QPainter painter(this);
paintFace(painter);
}
Second one is painted on a QPixmap, which in turn is blitted to widget:
void BufferedFace::paintEvent(QPaintEvent *ev)
{
QPixmap buffer(width(), height());
buffer.fill(Qt::transparent);
QPainter painter(&buffer);
paintFace(painter);
QPainter p(this);
p.drawPixmap(ev->rect(), buffer, ev->rect());
}
So far so good. I wanted to see how my app looks like on high resolution screen (I don't have one), so I set QT_SCALE_FACTOR=2 and run my app:
First face is sharp and crisp, whereas the seconf one is pixelated. That's because it is drawn to low resolution pixmap. So I have enlarged that QPixmap and set correct devicePixelRatio:
void BufferedFace::paintEvent(QPaintEvent *ev)
{
qreal pixelRatio = qApp->devicePixelRatio();
QPixmap buffer(width() * pixelRatio, height() * pixelRatio);
buffer.setDevicePixelRatio(pixelRatio);
buffer.fill(Qt::transparent);
QPainter painter(&buffer);
paintFace(painter);
QPainter p(this);
p.drawPixmap(ev->rect(), buffer, ev->rect());
}
Result:
Second face looks like it's drawn with correct resolution but then upscaled. Now I'm stuck. How to draw on QPixmap and then draw that QPixmap so it works correctly on Retina/HiDPI screens?
Whole application:
#include <QtWidgets>
class SmilingFace : public QWidget
{
public:
SmilingFace(QWidget *parent) : QWidget(parent) {};
void paintFace(QPainter &painter);
};
class DirectFace : public SmilingFace
{
public:
DirectFace(QWidget *parent) : SmilingFace(parent) {}
void paintEvent(QPaintEvent *ev) override;
};
class BufferedFace : public SmilingFace
{
public:
BufferedFace(QWidget *parent) : SmilingFace(parent) {}
void paintEvent(QPaintEvent *ev) override;
};
void SmilingFace::paintFace(QPainter &painter)
{
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::NoPen);
painter.setBrush(QBrush(Qt::lightGray));
painter.drawEllipse(1, 1, width()-2, height()-2);
painter.setPen(Qt::white);
painter.setFont(QFont("", 32));
painter.drawText(rect(), Qt::AlignHCenter, ";)");
}
void DirectFace::paintEvent(QPaintEvent *ev)
{
QPainter painter(this);
paintFace(painter);
}
void BufferedFace::paintEvent(QPaintEvent *ev)
{
QPixmap buffer(width(), height());
buffer.fill(Qt::transparent);
QPainter painter(&buffer);
paintFace(painter);
QPainter p(this);
p.drawPixmap(ev->rect(), buffer, ev->rect());
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
w.setWindowTitle("HiDPI");
DirectFace d(&w);
d.resize(48, 48);
d.move(16, 16);
BufferedFace i(&w);
i.resize(48, 48);
i.move(16 + 48 + 16, 16);
w.show();
return a.exec();
}
If you want HighDPI rendering, you should also use QRectF and QPointF arguments for the QPainter functions. In your paintFace(...) function adjust the drawEllipse and drawText functions to use QRectF arguments and not QRect. This might help.
It is not a good idea to use qApp->devicePixelRatio(). There are people with mixed HighDPI and Non-HighDPI monitors. As you are within a widget paintEvent(...) function, you can use directly the QWidget member function devicePixelRatioF(), and not the qApp->devicePixelRatio(). This will handle proper rendering of the widget, even when the user moves the widget in between monitors with a mixed resolution.
You should also enable High DPI scaling in Qt via: QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
Here comes the full solution, which renders the smiling face perfectly on HighDPI and Non-HighDPI screen, even when the widget is moved in between. Tested with Qt 5.9.2
#include <QtWidgets>
class SmilingFace : public QWidget
{
public:
SmilingFace(QWidget *parent) : QWidget(parent) {};
void paintFace(QPainter &painter);
};
class DirectFace : public SmilingFace
{
public:
DirectFace(QWidget *parent) : SmilingFace(parent) {}
void paintEvent(QPaintEvent *ev) override;
};
class BufferedFace : public SmilingFace
{
public:
BufferedFace(QWidget *parent) : SmilingFace(parent) {}
void paintEvent(QPaintEvent *ev) override;
};
void SmilingFace::paintFace(QPainter &painter)
{
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::NoPen);
painter.setBrush(QBrush(Qt::lightGray));
painter.drawEllipse(QRectF(1, 1, width() - 2, height() - 2));
painter.setPen(Qt::white);
painter.setFont(QFont("", 32));
painter.drawText(QRectF(0, 0, width(), height()), Qt::AlignHCenter, ";)");
}
void DirectFace::paintEvent(QPaintEvent *ev)
{
QPainter painter(this);
paintFace(painter);
}
void BufferedFace::paintEvent(QPaintEvent *ev)
{
qreal dpr = devicePixelRatioF();
QPixmap buffer(width() * dpr, height() * dpr);
buffer.setDevicePixelRatio(dpr);
buffer.fill(Qt::transparent);
QPainter painter(&buffer);
paintFace(painter);
QPainter p(this);
p.drawPixmap(ev->rect(), buffer, buffer.rect());
}
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication a(argc, argv);
QWidget w;
w.setWindowTitle("HiDPI");
DirectFace d(&w);
d.resize(48, 48);
d.move(16, 16);
BufferedFace i(&w);
i.resize(48, 48);
i.move(16 + 48 + 16, 16);
w.show();
return a.exec();
}
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"
I am faced with the problem of having to erase previously painted areas on a Qt widget.
The basic idea is, the user selects an area of the screen by clicking and dragging the mouse and a rectangle is drawn over the selected area.
The header
class ClearBack : public QWidget
{
Q_OBJECT
public:
explicit ClearBack(const QPoint &startingPos);
bool eventFilter(QObject *obj, QEvent *event);
void paintEvent(QPaintEvent *);
void mouseMoveEvent(QMouseEvent *event);
signals:
void regionSelected(const QRect &);
private:
QRect currentRegion;
};
The Implementation
ClearBack::ClearBack(const QPoint &startingPos)
{
setBackgroundRole(QPalette::Base);
installEventFilter(this);
currentRegion.setTopLeft(startingPos);
currentRegion.setBottomRight(startingPos);
this->setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint);
this->showMaximized();
}
void ClearBack::paintEvent(QPaintEvent * event)
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::black);
painter.drawRect(currentRegion);
}
void ClearBack::mouseMoveEvent(QMouseEvent *event)
{
QPoint currentPos(event->globalX(), event->globalY());
currentRegion.setBottomRight(currentPos);
this->repaint();
}
On a widget that has a solid background the effect works quite nicely, producing a single rectangle.
However, when the background is set to setAttribute(Qt::WA_TranslucentBackground); the following occurs.
The rectangles that were drawn previously are not "erased"
Is there a way to erase the previously painted rectangles on a translucent background, and if so, how?
Also for "bonus points" why does this effect occur on a translucent background and not on a solid one?
Widgets with WA_TranslucentBackground attribute do not clear their backgrounds automatically. You have to:
Change the composition mode from the default SourceOver to Source,
Explicitly clear the old rectangle with a transparent brush,
Paint the new rectangle.
Below is a working example, tested under Qt 5. You have to press the mouse to draw the initial rectangle and drag it around; the program exits when you release the mouse.
#include <QApplication>
#include <QWidget>
#include <QPainter>
#include <QMouseEvent>
class ClearBack : public QWidget
{
Q_OBJECT
QRect m_currentRegion, m_lastRegion;
public:
explicit ClearBack(const QPoint &startingPos) :
m_currentRegion(startingPos, startingPos)
{
setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);
showMaximized();
}
Q_SIGNAL void regionSelected(const QRect &);
protected:
void paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(Qt::transparent, 3));
painter.drawRect(m_lastRegion);
m_lastRegion = m_currentRegion;
painter.setPen(Qt::black);
painter.drawRect(m_currentRegion);
}
void mouseMoveEvent(QMouseEvent *event) {
m_currentRegion.setBottomRight(event->globalPos());
update();
}
void mouseReleaseEvent(QMouseEvent *) {
emit regionSelected(m_currentRegion);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ClearBack back(QPoint(200,200));
a.connect(&back, SIGNAL(regionSelected(QRect)), SLOT(quit()));
return a.exec();
}
#include "main.moc"