PDA

View Full Version : Painting within different Widgets and updating them individually



The EYE
5th January 2016, 10:48
Hello,

I'm new to Qt and I'm working on a program where I need to paint on different Widgets. In the end, I would like to display these widgets frameless and use the main window to change the paintings in these widgets step by step (text, picture, basic drawings). I'm using Qt 5.5 under Windows.

Currently my program consists of a main window with three buttons:

Start -> creates five Widgets (class OutputWindow)
Test -> paints within the main window
Quit -> ends the program

By clicking inside one of the five OutputWindow(s) (A, B, C, D and E), I can paint within each of these Widgets. Unfortunately, I can only paint within one Widget at a time. But I need to preserve the paintings in each Widget and change them individually. How can I preserve the current paintings while drawing on another Widget? (How should I handle update() / this situation?)

The second problem is that I'm not able to trigger the paintEvent for the OutputWindow(s) from within the Main Window. How can I use (for example) a button from the Main Window (class ControlWindow) to call a paintEvent from another class (OutputWindow) to paint on a Widget derived from this class?

I'm not sure if I've understod the use / reimplementation of paintEvent correctly :rolleyes:

To make my problem clear, I've attached some screenshots. And of course here is a part of the Code (you can find the whole project attached as a zip-file!):

controllerWindow.cpp | set variable true if button "Test" (btnTest) was pushed

void ControllerWindow::tempButtonPushed()
{
vtempButtonPushed = true;
update();
}

controllerWindow.cpp | paintEvent

void ControllerWindow::paintEvent(QPaintEvent*)
{
if (vtempButtonPushed == true)
{
QPainter painter(this);
QPen pen(Qt::red, 10, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin);

painter.setPen(pen);
painter.drawLine(0, 0, 800, 800);

vtempButtonPushed = false;
}
}

outputWindow.cpp | set variable true if mouse button was clicked

void OutputWindow::mousePressEvent(QMouseEvent*)
{
vtempButtonPushed = true;
update();
}

outputWindow.cpp | paintEvent

void OutputWindow::paintEvent(QPaintEvent*)
{
if (vtempButtonPushed == true)
{
QPainter painter(this);
QPen pen(Qt::blue, 10, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin);

painter.setPen(pen);
painter.drawLine(0, 0, 800, 800);

vtempButtonPushed = false;
}
}

Thank you in advance!
Max

anda_skoa
5th January 2016, 12:29
In your painEvent() you reset vtempButtonPushed back to false, so after one paint you reset the condition for painting.

So the next time paintEvent() is called you draw nothing.

If you want the line to be persistent, don't reset the condition that enables it :)

Cheers,
_

The EYE
6th January 2016, 13:07
Hey anda_skoa,
you're right! That's what I love about programming :p Simple errors can drive you crazy... Thanks a lot! :D
Conclusion: update() repaints all my widgets. If I want to "keep" a painting, I've to repaint it = don't reset the condition that enables it, as you've pointed out previously.

I'm using vtempButtonPushed as a variable to keep track of possible "states" (= different paintings). Is this a suitable way or may another way/method be a better approach for my intention? Here is the corresponding Code:

Using the mousPressEvent to go through the different states:

void OutputWindow::mousePressEvent(QMouseEvent*)
{
// vtempButtonPushed has 3 possible states (see paintEvent() above). If mouse button is clicked, move to the next state. After the state 3, draw nothing (vtempButtonPushed == 4) and move back to state 1 with the next click
if (vtempButtonPushed <=3)
{
vtempButtonPushed += 1;
}
else
{
vtempButtonPushed = 1;
}

update();
}
Checking the current state and choose the painting accordingly:

void OutputWindow::paintEvent(QPaintEvent*)
{
// vtempButtonPushed == 0 do nothing / 1 blue line / 2 red line / 3 green line
if (vtempButtonPushed == 1)
{
QPainter painter(this);
QPen pen(Qt::blue, 10, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin);

painter.setPen(pen);
painter.drawLine(0, 0, 800, 800);
}
else if (vtempButtonPushed == 2)
{
QPainter painter(this);
QPen pen(Qt::red, 10, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin);

painter.setPen(pen);
painter.drawLine(0, 0, 800, 800);
}
else if (vtempButtonPushed == 3)
{
QPainter painter(this);
QPen pen(Qt::green, 10, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin);

painter.setPen(pen);
painter.drawLine(0, 0, 800, 800);
}
}
This works for my outputWindow Widgets individually (see picture attached). Although I can control the individual behaviour of each Widget by using objectName() as part of my if-conditions like this:

if (vtempButtonPushed == 1 && (this->objectName() != "outputWindowA"))


Regarding my second question: How can I use (for example) a button from the Main Window (class ControlWindow) to call a paintEvent from another class (OutputWindow) to paint on a Widget derived from this class?
I had tried to connect a button, which is part of the Main Window, to the SLOT of another class (OutputWindow) which is not part of the form. It has not occured to me that this may not be possible within the designer and that I've to code that manually. :rolleyes: I've found this thread where someone else had the same problem (he although shows his solution): https://forum.qt.io/topic/16847/how-to-connect-a-forms-signal-to-application-class-slot/5
I'll try this approach and post again if I need some more assistance.

Thank you very much :)
Max

anda_skoa
6th January 2016, 16:17
Conclusion: update() repaints all my widgets.

update() schedules a paint event on the widget you call update on.
Usually that does not affect other widgets.



I'm using vtempButtonPushed as a variable to keep track of possible "states" (= different paintings). Is this a suitable way or may another way/method be a better approach for my intention?

That looks ok, though I would reset to 0 instead of going to 4 when you've reached the end, so that all values are either used in paintEvent() or at least appear in a comment.
Even better would be to use an enum with respectively named values instead of integer literals.



Although I can control the individual behaviour of each Widget by using objectName() as part of my if-conditions

While this works, it will get hard to understand quickly.
There are usually better ways to separate different behavior, but it really depends on what you need to be different.



Regarding my second question: How can I use (for example) a button from the Main Window (class ControlWindow) to call a paintEvent from another class (OutputWindow) to paint on a Widget derived from this class?

QWidget::update() is a slot, so you can simply connect the button's clicked signal to it.
But of course that will just result in paintEvent() being called, i.e. refreshing the already displayed drawing.



I've found this thread where someone else had the same problem (he although shows his solution): https://forum.qt.io/topic/16847/how-to-connect-a-forms-signal-to-application-class-slot/5

My recommendation would be to use explicit connects instead, see QObject::connect().

Cheers,
_

The EYE
11th January 2016, 09:36
update() schedules a paint event on the widget you call update on.
Usually that does not affect other widgets.
The program example in my first post showed exactly this behaviour: update() affected all widgets, no matter from which class they derived from. That's why it was a problem that I'd reset the "paint condition". If update() wouldn't have had an affect on the other widgets, the missing "paint condition" wouldn't have mattered, because Qt wouldn't have called the paintEvent. At least this is the way I see it. Did I get something wrong?



That looks ok, though I would reset to 0 instead of going to 4 when you've reached the end, so that all values are either used in paintEvent() or at least appear in a comment.
Even better would be to use an enum with respectively named values instead of integer literals.
Good point! I've changed it - now it goes from 0 to 3. And I'm going to look into the possibilities using enum. But I'm not sure if I can come up with some meaningful descriptions for the different states.



While this works, it will get hard to understand quickly.
There are usually better ways to separate different behavior, but it really depends on what you need to be different.
In the end the five (black) widgets are going to show the following things:

some (blinking) drawings to show the user something (by using a projector in the real world)
a progessbar
instructions on what to do
again some drawings to assist the user in the real world
a picture

The user has to be able to move through this process step by step. That's why I'm not sure if I can come up with anything better to descripe the current state instead of the int values.

I'm thankful for any ideas and suggestions on how to tackle this :-) As always: The simpler, the better! I would like to program it in a way that allows other people to understand it quickly.



QWidget::update() is a slot, so you can simply connect the button's clicked signal to it.
But of course that will just result in paintEvent() being called, i.e. refreshing the already displayed drawing.
Good to know, thanks! But, like you've said, only calling update() is not enough.



My recommendation would be to use explicit connects instead, see QObject::connect().
Thank you, I'm going to check on that right now.

I although attached my current example code to make helping easier for everybody :)

Thank you once again, I'm looking forward to further contributions of the forum!
Max

anda_skoa
11th January 2016, 10:30
The program example in my first post showed exactly this behaviour: update() affected all widgets, no matter from which class they derived from. That's why it was a problem that I'd reset the "paint condition". If update() wouldn't have had an affect on the other widgets, the missing "paint condition" wouldn't have mattered, because Qt wouldn't have called the paintEvent. At least this is the way I see it. Did I get something wrong?

Usually only the widget you are calling update() on will get a paint scheduled, but there could be internal mechanisms or even parts of the windowing system that trigger additional updates.
Or to rephrase it differently: calling update() on one widget is not guaranteed to cause a repaint of all others.



In the end the five (black) widgets are going to show the following things:

some (blinking) drawings to show the user something (by using a projector in the real world)
a progessbar
instructions on what to do
again some drawings to assist the user in the real world
a picture

The user has to be able to move through this process step by step. That's why I'm not sure if I can come up with anything better to descripe the current state instead of the int values.

Hmm, but if they draw different things, maybe they should be different widgets? Each implementing its respective "workflow"

If some things are the same, e.g. all widgets have four states and advance the state on mouse press, then that part could be split into a small helper class that each of the widgets then uses for state tracking.



Good to know, thanks! But, like you've said, only calling update() is not enough.

Right. Originally you asked about triggering paintEvent(), that is what update() does.
What do you want to do, what should happen, when the button is clicked?

Cheers,
_

The EYE
11th January 2016, 11:08
Usually only the widget you are calling update() on will get a paint scheduled, but there could be internal mechanisms or even parts of the windowing system that trigger additional updates.
Or to rephrase it differently: calling update() on one widget is not guaranteed to cause a repaint of all others.
Thanks for the explanation! I'll test this behaviour with Linux if I find the time. Maybe Windows is the problem/unknown part here. Anyway, this project is going to run on Windows so I've to work with that.



Hmm, but if they draw different things, maybe they should be different widgets? Each implementing its respective "workflow"

If some things are the same, e.g. all widgets have four states and advance the state on mouse press, then that part could be split into a small helper class that each of the widgets then uses for state tracking.
In the beginning, I thought it would be useful to let them derive from the same class to make the code easier to read (because they have the same properties like backgroundcolor and so on). On second thought (thanks ;)) this would make the code harder to read because of the complicated if-conditions and so on.

The helper class sounds very promissing! :D Great idea! So I could split up the different widgets into different classes and use a helper class to keep track of the current state. This would allow me to manage the behaviour of each widget within "there code-area" instead of (potentially confusing) if-conditions and so on.



Right. Originally you asked about triggering paintEvent(), that is what update() does.
What do you want to do, what should happen, when the button is clicked?
I've got you wrong. You're absolutely right. I ment changing the paint condition (value of vtempButtonPushed) and schedule a paintEvent afterwards.

After lunch, I'll try to implement QObject::connect().

Thanks!
Max

anda_skoa
11th January 2016, 12:01
bsolutely right. I ment changing the paint condition (value of vtempButtonPushed) and schedule a paintEvent afterwards.

Ah, that should be easy.

Just add a custom slot, in which you change the state and call update().

Cheers,
_