PDA

View Full Version : Painting on a QLabel



davethomaspilot
13th July 2012, 20:42
I want to draw points on a label imbedded in a widget.

So, in the paintEvent for the widget I create a QPainter and pass it a pointer to a QPixmap I got from the label using QPixmap::fromImage().

I draw points using the painters drawPoint(int x, int y) method. When done with all the drawing, I use the label's setPixmap method, passing QPixmap referenced by the pointer passed to the QPainter constructor.

All of this works, except the setPixmap seems to partially break the Qt's event processing. I can still move windows and do many GUI things, but, for example, radio buttons don't uncheck after changing what's checked unless I move the window.

If I remove the setPixmap from the paintEvent handler, all the event processing works normally, but the points don't show up on the label.

Here are some code fragments (I'm a Qt newbie, and here it probably shows!)



void PlaceStartLine::paintEvent(QPaintEvent *event)
{
QPainter painter(&qp);

QPen myPen(Qt::red, 5, Qt::SolidLine,Qt::RoundCap);
painter.setPen(myPen);
good_rows.clear();

for (int i=0; i<tableWidget->rowCount();i++)
{
bool ok_x;
bool ok_y;
bool ok_phys_x;
bool ok_phys_y;
double phys_x;
double phys_y;
int x;
int y;



x= tableWidget->item(i,2)->text().toInt(&ok_x);
if (ok_x)
tableWidget->item(i,2)->setBackgroundColor(Qt::green);
else
tableWidget->item(i,2)->setBackgroundColor(Qt::red);

y= tableWidget->item(i,3)->text().toInt(&ok_y);

if (ok_x)
tableWidget->item(i,3)->setBackgroundColor(Qt::green);
else
tableWidget->item(i,3)->setBackgroundColor(Qt::red);

phys_x = tableWidget->item(i,0)->text().toDouble(&ok_phys_x);
if (ok_phys_x)
tableWidget->item(i,0)->setBackgroundColor(Qt::green);
else
tableWidget->item(i,0)->setBackgroundColor(Qt::red);

phys_y = tableWidget->item(i,1)->text().toDouble(&ok_phys_y);
if (ok_phys_y)
tableWidget->item(i,1)->setBackgroundColor(Qt::green);
else
tableWidget->item(i,1)->setBackgroundColor(Qt::red);

if (! (ok_x && ok_y)) continue;

good_rows.push_back(i);
painter.drawPoint(x,y);

}
startLineImage->setPixmap(qp);
qp.scaled(grabbedImage->size(),Qt::KeepAspectRatio);

//startLineImage->setPixmap(qp);


//event->accept();
}




If I uncomment the line:

startLineImage->setPixmap(qp);

Then the points get drawn, but event processing seems crippled. If I omit the line, everything works fine, but I don't see the drawn points, presumably because they are drawn on a copy of the labels pixmap, so I need to do a stPixmap(qp) after drawing.

I played with adding the event->accept(), but it made no difference that I could tell.

Anybody have a clue on what's going on and/or how to fix?

Thanks,

Dave Thomas

Added after 4 minutes:

Ok, I fixed it, but I don't totally understand.

I added code to just do the painting one time, when I know something changed and everything works nicely now.

I guess there was just too much to do for EACH paint event?

Dave Thomsa

d_stranz
14th July 2012, 23:59
Are you sure that your code wasn't resulting in a recursive (or maybe just multiple) paint event? If you do something inside the paint event which results in the widget thinking it has been resized or needs to be repainted, this could cause that.

davethomaspilot
15th July 2012, 00:33
Hmm, not that I know of it, but good point.

The code for the QPaint is above. Would anything there cause the QPaint to be executed again before it completed? It set colors of cells, would that cause another QPaint event for the running one completed?

Thanks,

Dave Thomas

wysota
15th July 2012, 01:35
Would anything there cause the QPaint to be executed again before it completed? It set colors of cells, would that cause another QPaint event for the running one completed?

Yes :) Half of your code above is forbidden to put in a paint event. paintEvent() of widget X is supposed to draw the contents of widget X, not do many other things that have nothing to do with drawing widget X.

davethomaspilot
15th July 2012, 02:06
Ok, makes sense.

Thanks!

Dave THomas

davethomaspilot
16th July 2012, 13:01
I removed everthing from the QPaintEvent except the code that:

gets an x & y from a tableWidget;
draws a point on (a copy?) of a labels QPixmap using that x, y.
assigns the QPixmap with the drawn points to a label

I still get the same crippled event handling, unless I add a flag for the method to be executed only once.

Here's the paintEvent handler:




void PlaceStartLine::paintEvent(QPaintEvent *event)
{

//if(repainted) return;

QPainter painter(&qp);

QPen myPen(Qt::red, 5, Qt::SolidLine,Qt::RoundCap);
painter.setPen(myPen);


for (int i=0; i<tableWidget->rowCount();i++)
{
bool ok_x;
bool ok_y;

int x;
int y;

x= tableWidget->item(i,2)->text().toFloat(&ok_x);
y= tableWidget->item(i,3)->text().toFloat(&ok_y);

if (ok_x && ok_y)
painter.drawPoint(x,y);

}
qp.scaled(grabbedImage->size(),Qt::KeepAspectRatio);
startLineImage->setPixmap(qp);

repainted=true;

}


If recursion is the issue, then it's the setPixmap method that causes it. How does one draw on a label, if you can't associate the PixMap drawn on to a label?


Half of your code above is forbidden to put in a paint event.

Where can I find a definition of what is forbidden to put in a paint event?

Thanks!

Dave Thomas

wysota
16th July 2012, 13:22
It's still wrong. You can't call setPixmap from within a paint event. I think you misunderstand what paintEvent is supposed to do. If you have a paintEvent of a widget class "X" then the code you put in there should paint the canvas of the instance of widget "X". It should not manipulate properties of any objects and is not meant to do anything with children of that class, including that it is supposed to set what the contents of child widgets should look like. To me it seems the code you have should simply be put in a different function, most probably in a slot connected to a signal that is emitted whenever data in tableWidget changes.

As for what's forbidden -- a good rule of a thumb is that you should not call any non-const functions other than ones manipulating local variables declared in the paintEvent body. In your case you can manipulate the "painter" object but not the "startLineImage" object.

davethomaspilot
16th July 2012, 17:17
If I don't call setPixmap, the label's pixmap doesn't get updated with the new drawn points. That's just drawing on a copy of the labels pixmap.

So, my question is still

"How does one draw on a label, if you can't associate the PixMap you draw on to the label"?

In other words, if I don't do the setPixmap call in the paint event handler, nothing that is drawn is ever seen.

Thanks,

Dave Thomas

Maybe a key assumption I'm making isn't valid.

I thought the only place you could use the draw methods of a Painter was in the paint event handler?

It that's not ture, then sure, I can do the draw and the assignment of the newly update pixmap to the label outside the handler.

Thanks,

Dave Thomas

wysota
16th July 2012, 17:21
"How does one draw on a label, if you can't associate the PixMap you draw on to the label"?
You can but not in paintEvent. Do it wherever else you want.


I thought the only place you could use the draw methods of a Painter was in the paint event handler?
Only if you draw on a widget. Here you're drawing on a pixmap.

I don't know what your particular usecase is but I'd like to present an alternative, simpler, approach. Don't use a label at all -- draw directly on the widget (of course this time from within the paint event).

davethomaspilot
16th July 2012, 17:52
"You can but not in paintEvent. Do it wherever else you want."

But if you can only draw in the paintEvent, the setPixMap has to be AFTER the points are drawn. How does one make sure that happens after the draws occur, if you don't set the pixmap in the event handler?


The points need to be drawn on the Pixmap. They correspond to physical x,y locations mapped to specific points on the Pixmap, with a coordinate system referenced to the Pixmap, not the widget.

wysota
16th July 2012, 19:08
"You can but not in paintEvent. Do it wherever else you want."

But if you can only draw in the paintEvent, the setPixMap has to be AFTER the points are drawn. How does one make sure that happens after the draws occur, if you don't set the pixmap in the event handler?


The points need to be drawn on the Pixmap. They correspond to physical x,y locations mapped to specific points on the Pixmap, with a coordinate system referenced to the Pixmap, not the widget.

You have fixed yourself on drawing the points in the event handler. Here is a sample app, study it:


class Widget : public QLabel {
public:
Widget() : QLabel() {
m_px = QPixmap(400, 400);
m_px.fill(Qt::transparent);
setPixmap(m_px);
}
void addPoint(const QPoint &pt) {
QPainter p(&m_px);
p.drawPoint(pt);
p.end();
setPixmap(&m_px);
}
protected:
void mousePressEvent(QMouseEvent *e) {
addPoint(e->pos());
}
QPixmap m_px;
};

or similar without using QLabel at all:


class Widget : public QWidget {
public:
Widget() : QWidget() {}

void addPoint(const QPoint &pt) {
m_pts << pt;
update();
}
protected:
void mousePressEvent(QMouseEvent *e) {
addPoint(e->pos());
}
void paintEvent(QPaintEvent *e) {
QPainter p(this);
p.drawPoints(m_pts);
}
QPolygon m_pts;
};

plus, of course, main():

int main(int argc, char **argv) {
QApplication app(argc, argv);
Widget w;
w.show();
return app.exec();
}

davethomaspilot
16th July 2012, 19:51
Thanks for takingl the time to send the example code snippets. That's certainly above and beyond the call of duty!

The key point I didn't understand was that you could create a QPainter outside a paintEvent handler and do the drawing there. So, I really don't even need a paintEvent handler.

For what appears to be no good reason, the examples I found instantiated a QPainter inside the paintEvent handler, so I just did it that way.

So now, I see no need in my application to have a paintEvent handler. Just create a QPainter, draw the points on a Pixmap, and assign the Pixmap to the label. Like you did in your first code snippet.

I'm missing your point about not using a label and why that's simpler. In my case, I have a widget with a table, buttons, and a label that's just an image. The coordinate system for the draw points is relative to the label's position, not the containing widget's position.

I guess you're just saying that you can do the drawpoints in a paintEvent handler, if you're painting on somthing like a QPolygon. Then you don't have to worry about copying what you just painted on using something like a setPixmap. But it's still easier to not use the paintEvent at all!

Why does your second example need the paintEvent. Couldn't those two lines of code be in your add_points method?

Thanks,

Dave Thomas




Thanks,

Dave

wysota
16th July 2012, 20:17
For what appears to be no good reason, the examples I found instantiated a QPainter inside the paintEvent handler, so I just did it that way.
... and they passed "this" as the argument for the constructor. That's the key point here.


I'm missing your point about not using a label and why that's simpler. In my case, I have a widget with a table, buttons, and a label that's just an image. The coordinate system for the draw points is relative to the label's position, not the containing widget's position.
There are many reasons and there is no need to discuss them here. If using an intermediate pixmap works for you then that's fine.


I guess you're just saying that you can do the drawpoints in a paintEvent handler,
I suggest you take a look at source code of some custom widgets, it might open a whole new world for you.


if you're painting on somthing like a QPolygon.
I'm not painting on a QPolygon (it's just a vector of points). I'm painting on a widget.


But it's still easier to not use the paintEvent at all!
You are using paintEvent. You just didn't implement it yoruself. If your points change very often then your current implementation will be twice as slow (or worse on X11) than my second implementation because you're painting everything twice -- once points on a pixmap and then pixmap on the widget.


Why does your second example need the paintEvent. Couldn't those two lines of code be in your add_points method?
No. paintEvent() is called whenever the widget needs redrawing and that can happen because of reasons other than points being added (e.g. when you resize the window, minimize it or sometimes even move some other window over it. In other words paintEvent() is not meant to update the state of the widget when something changes, it is meant to visualize the current state of the widget (whatever it might be) -- it has no memory and no sense of "change". It just lives now and today.