I use QGraphicsView, QGrapichsScene and QGraphicsItem for drawing some charts. I have implemented QGraphicsItem::paint function for drawing text (values of charts), but it is not being called every time that is must draw something new. My paint function
void CLabelItem::paint(QPainter *painter,
const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget = 0*/)
{
if ( GetValue() < 0 )
{
return;
}
painter->drawText(boundingRect(), m_value.toString());
}
So my question is - why QGraphicsItem::paint can be not called and how may I make it to be called?
You need to call QGraphicsItem::update() from the function that modify the m_value variable to trigger a repaint.
In general with Qt graphics items are drawn when paintEvent is called, and then you can handle painting inside a paint function.
Related
I am trying to create a simple painting application - it is just supposed to draw when you click and drag a cursor (just like Paint). I know I have to use QPainter but how can I handle it? How to do it? Any help would be really appreciated. I tried lurking through internet but didn't find too much info (I daw drawing lines etc. by code that you launch an app and it is here but I can not find an example of drawing something by user).
Here's Jeremy's answer in code instead of prose:
// https://github.com/KubaO/stackoverflown/tree/master/questions/simplepaint-39358392
#include <QtWidgets>
// Make a subclass of the QWidget class, so that you can override some of its
// virtual methods
class PaintWidget : public QWidget {
// Create a QPixmap object that you will use to store the bitmap
// that the user will draw [on].
QPixmap m_pixmap;
QPoint m_lastPos;
// Override the paintEvent(QPaintEvent *) [...]
void paintEvent(QPaintEvent *) override {
QPainter painter{this};
painter.drawPixmap(0, 0, m_pixmap);
}
void resizeEvent(QResizeEvent *) override {
// [...] size the QPixmap to be at least as big as the maximum size of the window
// We'll also never let it shrink so as not to lose the already drawn image.
auto newRect = m_pixmap.rect().united(rect());
if (newRect == m_pixmap.rect()) return;
QPixmap newPixmap{newRect.size()};
QPainter painter{&newPixmap};
painter.fillRect(newPixmap.rect(), Qt::white);
painter.drawPixmap(0, 0, m_pixmap);
m_pixmap = newPixmap;
}
// Override the mousePressEvent(QMouseEvent *) [...]
void mousePressEvent(QMouseEvent * ev) override {
m_lastPos = ev->pos();
draw(ev->pos());
}
// Override the mouseMoveEvent(QMouseEvent *) [...]
void mouseMoveEvent(QMouseEvent * ev) override {
draw(ev->pos());
}
void draw(const QPoint & pos) {
QPainter painter{&m_pixmap};
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen({Qt::blue, 2.0});
painter.drawLine(m_lastPos, pos);
m_lastPos = pos;
update();
}
public:
using QWidget::QWidget;
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
// Create an object of your subclass and call show()
PaintWidget ui;
ui.show();
return app.exec();
}
It is not necessary to override the mouseReleaseEvent. In a widget, the default behavior is to track the mouse movement only when a mouse button is depressed. The mouseMoveEvent won't be called unless a button is depressed.
It's a pretty broad question, but here are the basics:
Make a subclass of the QWidget class, so that you can override some of its virtual methods later on.
Create an object of your subclass and call show() on it (just before calling QApplication::exec()). This object will appear on screen as a very simple window, and it will serve as your user's painting-surface.
Create a QPixmap object that you will use to store the bitmap that the user will draw. Make sure to size the QPixmap to be at least as big as the maximum size of the window that you want to support. Call fill() on the QPixmap to fill it with your favorite background color.
Override the mousePressEvent(QMouseEvent *) method of your object to set a boolean is_mouse_down flag, and also to record the current position of the mouse pointer within the window (by calling pos() on the QMouseEvent object that gets passed in to the mousePressEvent() call and storing that into a member variable of your object).
Override the mouseMoveEvent(QMouseEvent *) method so that if is_mouse_down_is set to true, it creates a QPainter object on the stack -- pass a pointer to the QPixmap to the QPainter object's constructor so that the QPainter will draw into your QPixmap object. Then call drawLine() on the QPainter object to draw a line from the previous mouse position to your current one. Finally, call update() to tell Qt to call paintEvent() for you ASAP.
Override the mouseReleaseEvent(QMouseEvent *) method to set is_mouse_down to false again
Override the paintEvent(QPaintEvent *) method to create a QPainter object on the stack -- pass a pointer to (this) to the QPainter object's constructor, so that it will paint onto the QWidget directly. Then call drawPixmap() on the QPainter object so that it will draw your QPixmap object onto the widget's visible surface.
If you'd like to see a pre-written example, check out the Scribble application included with Qt, in $QTDIR/examples/widgets/widgets/scribble.
This is my first time using Qt and I have to make a MSPaint equivalent with Qt. I am however having trouble with painting my lines. I can currently draw a line by clicking somewhere on the screen and releasing somewhere else, however when I draw a second line the previous line is erased. How could I keep the previously painted items when painting another item?
void Canvas::paintEvent(QPaintEvent *pe){
QWidget::paintEvent(pe);
QPainter p(this);
p.drawPicture(0,0,pic);
}
void Canvas::mousePressEvent(QMouseEvent *mp){
start = mp->pos();
}
void Canvas::mouseReleaseEvent(QMouseEvent *mr){
end = mr->pos();
addline();
}
void Canvas::addline()Q_DECL_OVERRIDE{
QPainter p(&pic);
p.drawLine(start,end);
p.end();
this->update();
}
Canvas is a class that derives QWidget, it has 2 QPoint attributes start and end.
Class body:
class Canvas : public QWidget{
Q_OBJECT
private:
QPoint start;
QPoint end;
QPicture pic;
public:
Canvas(){paint = false;setAttribute(Qt::WA_StaticContents);}
void addline();
protected:
void paintEvent(QPaintEvent *);
void mousePressEvent( QMouseEvent * );
//void mouseMoveEvent( QMouseEvent * );
void mouseReleaseEvent( QMouseEvent * );
};
QPicture records QPainter commands. Also from its documentation you can read this:
Note that the list of painter commands is reset on each call to the
QPainter::begin() function.
And the QPainter constructor with a paint device does call begin(). So each time the old recorded commands are deleted.
It may sound tempting to use it, since it does say a few good things, for example, that it is resolution independent, but this is not how drawing applications work in reality. Switch to a QPixmap and your drawings will persist.
Also, don't forget to initialize the pixmap, because by default it will be empty and you will not be able to draw on it.
Canvas() : pic(width,height) {...}
Furthermore, if you would like the introduce the concept of brushes as in artistic brushes and not QBrush, you might want to look at this approach to draw the line.
EDIT: Note that you should be able to prevent QPicture from losing its content by not calling begin() on it more than once. If you create a painter, dedicated to only drawing on it at class scope, and call begin in the constructor, different recorded drawing operations should persist. But as their number increases it will take more and more time to draw the QPicture to your widget. You could come around that by using both a QPicture and a QPixmap, and draw to both, use the picture to record the actions and the pixmap to avoid continuously redrawing the picture, even though you will do double the work it will still be more efficient, while you still retain the possibility to use the picture to re-rasterize in a different resolution or save the drawing history. But I doubt QPicture will do well as your drawing application begins to take shape of an actual drawing application, for example when you start using pixmap brushe stencils and such.
I am creating a simple gauge in Qt 4.7.4, and everything is working wonderfully. Except for the fact that, for the life of me, I cannot get the dial shape to paint over the text labels when it passes over them. It always paints it behind the label. I am just using a simple drawpolygon() method.
I'm thinking this has something to do about paint events? I am drawing everything inside a QFrame inside a MainWindow. I am using QFrame's paintEvent.
Edit:
The QLabels are created on start up with new QLabel(this). They are only created once, and never touched again ( Similar to manually adding them on the Ui with Designer). The drawpolygon() is in the QFrame's Paint event.
"myclass.h"
class gauge : public QFrame
{
Q_OBJECT
public:
explicit gauge(QWidget *parent = 0);
~gauge();
void setValues(int req, int Limit, bool extra=false);
private:
void drawDial();
protected:
void paintEvent(QPaintEvent *e);
};
"myclass.cpp"
void gauge::paintEvent(QPaintEvent *e)
{
Q_UNUSED(e);
drawDial();
return;
}
void gauge::drawDial()
{
QPainter Needle(this);
Needle.save();
Needle.setRenderHint(Needle.Antialiasing, true); // Needle was Staggered looking, This will make it smooth
Needle.translate(centrePt); // Center of Widget
Needle.drawEllipse(QPoint(0,0),10,10);
Needle.restore();
Needle.end();
}
If the gauge widget and the QLabels are siblings, then you can move the gauge widget to the front by calling its raise() method.
If the QLabels are children of the gauge widget, on the other hand, then they will always display in front of it. In that case you can either reorganize your widget hierarchy so that they are siblings instead, or you can get rid of the QLabels and simply call drawText() from your paintEvent() method instead (after drawDial() returns)
I have my own QGraphicsScene and two QGraphicView’s. In the QGraphicsScene I use the
drawForeground(QPainter *painter, const QRectF &rect)
function to draw a grid. Now I want the grid only to be visible in the first, but not in the second view…is this possible?
The QGraphicsView is a window into a world (the QGraphicsScene). What you're asking would be like saying that it's raining when I look outside my windows, but can I have it only rain when I look through one of them?!
However, you can change the curtains(!), so override the function of the QGraphicsView, rather than the QGraphicsScene. I suggest using this: -
QGraphicsView::drawForeground(QPainter *, const QRectF &);
Apply this just to the view you want to change. With two views, you'll need a flag to set which of the views you want to apply this to. For example: -
void MyGraphicsView::drawForeground(QPainter* painter, const QRectF& rect)
{
QGrahicsView::drawForeground(painter, rect);
if(m_bDrawGrid)
DrawGrid();
}
So it appears that Qt4 doesn't let you draw on windows outside of a paint event. I have a lot of code that expects to be able to in order to draw rubber band lines (generic drawing code for a particular, proprietary interface that I then implement in the given UI). I've read about the pixmap method, it would be a lot of work and I don't think it's really what I want.
Is there a workaround that allows me to do what I want anyway? I just need to draw XOR bands on the screen.
Tried the WA_PaintOutsidePaintEvent flag. Then I saw the bit that says it doesn't work on Windows.
In modern compositing desktops window painting needs to be synchronized by the window manager so that the alpha blending and other effects can be applied, in order, to the correct back buffers - the result of which is then flipped onto the screen to allow tear-free window animations.
Invoking painting operations out-of-band of this process - while supported for legacy reasons on the underlying platforms - would subvert this process and cause a number of very non optimal code paths to be executed.
Basically, when you have painting to do on a window: Call the invalidate function to schedule the painting soon, and paint during the paint event.
Just paint to a QPixmap, and copy it to the real widget in the paintEvent. This is the only standard way. You shouldn't try to workaround it.
Seems like if you could get access to the Hwnd of the window in question, you could paint on that surface. Otherwise, I'm not sure. If by pixmap method you mean something like this, I don't think it's a bad solution:
m_composed_image = QImage(size, QImage::Format_ARGB32);
m_composed_image.setDotsPerMeterX(dpm);
m_composed_image.setDotsPerMeterY(dpm);
m_composed_image.fill(Qt::transparent);
//paint all image data onto new image
QPainter painter(&m_composed_image);
painter.drawImage(QPoint(0, 0), m_alignment_image);
As it's mentioned in one of the answers, The best way to do it will be to make a pixmap buffer. The painting works will be done in the buffer and when it's done, repaint() will be scheduled. And the paintEvent() function just paints the widget by copying the pixel buffer
I was trying to draw a circle on a widget area after user inputs values and pushes a button. This was my solution. connecting the drawCircle() slot to the clicked() signal.
class PaintHelper : public QWidget
{
Q_OBJECT
private:
QPixmap *buffer;
public:
explicit PaintHelper(QWidget *parent = 0) : QWidget(parent)
{
buffer=new QPixmap(350,250);// this is the fixe width of this widget so
buffer->fill(Qt::cyan);
}
signals:
public slots:
void drawCircle(int cx, int cy, int r){
QPainter painter(buffer);
painter.setBrush(QBrush(QColor(0,0,255)));
// A part of mid-point algorithm to draw 1/8 pacrt of circle
int x1=0,y1=r;
int p=1-r;
for(int i=0;y1>=x1;i++){
painter.drawPoint(x1+cx,y1+cy);
x1++;
if(p>0){
p+=3+x1;
}
else{
y1--;
p+=2*x1-2*y1;
p++;
}
}
this->repaint();
}
// QWidget interface
protected:
void paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawPixmap(0,0,*buffer);
}
};