PDA

View Full Version : update the QProgressBar



aloha
14th April 2009, 21:25
Hello,

I'm building a gui over a library i have created.
I wanted to insert a progressbar. So what i was planning to do was: enter another parameter (int) by reference/pointer to the different functions i have in my library, so i can update that value while the procedure is running. This wont be such a problem I think.

But then: how can i tell the value has changed?
I thought about adding a timer in a thread who checks that value every 1 second or so.
Ok, so far so good: i created a QThread holding a timer but then i wanted to send a signal to the progressbar so the value is updated on the screen.

I added a signal in the thread and connected it, but appearently i can't define a signal in a qthread?

This seems a complex procedure to integrate this progressbar in the gui and to make it work.

Could there be a more convenient solution?

Thanx

Ginsengelf
15th April 2009, 07:53
but appearently i can't define a signal in a qthread?
You can, but I don't think you need a thread at all. Just create the timer in your main window.

Ginsengelf

wysota
15th April 2009, 11:11
Can we see the code?

aloha
15th April 2009, 11:40
Ok, the app is quite big, so i'll try to get rid of all the details.

From a tab i can initiate a filter procedure; going like this:
And added a timer


int * state=new int(0);
timerThread * theThread = new timerThread(); // create new timer
theThread->setValues(m_ui->progressBar, state); // add the progressbar to update and the value to display (by pointer)
theThread->run(); // run the thread who will update every 1 sec
ds->Filter(min, max, state); // and initiate the filter procedure


As you can see, i added a pointer to the progressbar to the timer, so i dont need a signal

When we look at run procedure the timerThread:


void timerThread::run() {
timer = new QTimer(); // initiate a new timer
timer->setInterval(1000); // every 1 sec
timer->setSingleShot(false); // keep looping untill i explicitly stop it
timer->start();
if ((*status) == 100) { //only stop the timer untill the procedure finished
timer->stop();
}
else {
theBar->setValue(*status); // not 100% yet => display the current value and automatically the timer will restart
}
}


It's too bad i now have to add a pointer to an integer to every function i have in my library, just to know the progress. but there most likely is no other way to do it.

Should i encapsulate my filter procedure in a QThread, cause it doesnt seem to update now.

Thank you

wysota
15th April 2009, 12:01
You can't access widgets from worker threads. Furthermore if you have a timer, you don't need a separate thread unless you are doing heavy calculations in the main thread but then your user interface will freeze regardless of using threads because widgets need to be handled in the main thread.

You should really use the signal-slot mechanism to communicate between the progress bar and its environment. Having a shared integer variable won't do you any good. Should using signals and slots not be possible in your case, use events instead - post a custom event to the progress bar and handle the event there changing the value shown by the bar. Also remember not to starve the event loop or you won't see any progress on the bar as it will not update.

aloha
15th April 2009, 14:19
Should using signals and slots not be possible in your case, use events instead - post a custom event to the progress bar and handle the event there changing the value shown by the bar. Also remember not to starve the event loop or you won't see any progress on the bar as it will not update.

Thanks for your reply, i'll check out the events. If I'm correct, using events, I don't have to litter my library code with any qt related code and I just have to send every second (depending on the timer) a custom event to the progressbar.

wysota
15th April 2009, 17:32
With signals and slots you don't have to litter your code as well. You can always call the setValue() method (if used from the main thread) or use QMetaObject::invokeMethod with QueuedConnection parameter (from worker threads).

aloha
15th April 2009, 18:37
This is just some test code:



timerThread::timerThread() {
timer = new QTimer();
connect( timer, SIGNAL(timeout()), this, SLOT(sendValue()));
}

void timerThread::setValues(QProgressBar * s, int * state) {
status=state;
theBar=s;
}


void timerThread::run() {
timer->setInterval(1000);
timer->start();
}
*/
}

// this will be executed when the timer has finished
void timerThread::sendValue() {
if (*status <= 100) {
timer->start();
}
theBar->setValue(*status);
}


I can't see how you think this will work without putting the timer in a thread.
As the timer and the filter process have to run simultaneously.
Now i get an error when i run it: it cant seem to find the slot 'sendValue()'


Object::connect: No such slot QThread::sendValue() in timerThread.cpp:6


Any ideas?

wysota
16th April 2009, 00:59
I'm guessing you forgot to add the Q_OBJECT macro to the thread class but the real problem is accessing the widget from within a worker thread which is forbidden. Also remember the thread object lives in the thread that created the object and not in the thread that the object controls. Therefore slots from this object will be executed in context of the main thread which is probably blocked by some heavy calculation or else you wouldn't be spawning the worker thread at all.

aloha
16th April 2009, 18:18
I cant seem to find much information about Qevents.
I think i still need signal and slot communication in my timer because I've got to know when the timer time's out. Or can I also do that using slots, maybe?

Imagine that this works, then i still need to send a slot to the mainwindow. Which i had to pass on by pointer to the thread, so then i guess I'm still accessing a widget within a worker thread which is forbidden?

Looking at my previous post, can anybody tell me where just to place those QEvents, cause it seems harder than expected.

Thank you!

wysota
17th April 2009, 09:02
I think i still need signal and slot communication in my timer because I've got to know when the timer time's out. Or can I also do that using slots, maybe?
Yes, you can. See QObject::startTimer(), QObject::killTimer() and QObject::timerEvent().


Imagine that this works, then i still need to send a slot to the mainwindow. Which i had to pass on by pointer to the thread, so then i guess I'm still accessing a widget within a worker thread which is forbidden?
Sending a signal doesn't involve accessing the receiver directly. Only making the connection does but it is thread safe.


Looking at my previous post, can anybody tell me where just to place those QEvents, cause it seems harder than expected.

See docs for QObject::customEvent() and follow the links.

aloha
17th April 2009, 12:48
In my filter call I know have the following:


state=new double(0); // initialize the member variable
cout << *state << endl; // prints 0
updateProgressBar * me=new updateProgressBar(state, this); // this holds the timer and the events with a pointer to the state

ds->Filter(min, max,s, state); // pass the same pointer to state to check on the progress


the updateProgressBar code:


updateProgressBar::updateProgressBar(double* state, QObject *parent)
: status(state), pa(parent), QObject(parent) // initialize
{
startTimer(20); // update every 20 miliseconds
}

// this will be called when the timer times out
void updateProgressBar::timerEvent(QTimerEvent *event)
{
std::cout << "Timer ID:" << event->timerId() << std::endl;
if (*status==100) { // finished?
killTimer(event->timerId());
}
std::cout << "sending " << *status << std::endl; // !!!
const QEvent::Type MyEvent = (QEvent::Type)1234;
QEvent * message=new QEvent(MyEvent);
QCoreApplication::sendEvent(pa, message);
}


And i receive the event in my parent frame:



void intensityframe2::customEvent(QEvent * events) {
cout << "received an event" << endl;
int value=(*state); //cast to int
m_ui->progressBar->setValue(value); //display on the screen
}

This code works, but i've got one pointer problem.
After even one timer loop, i display the value of the pointer (see !!!) and then i send an event to the parent window so he knows he has to check on the value of the state/progress and display it. But even when no assignments have been made to value of the pointer is set to a strange value: like for example: 2.1305e-313

The value of that double is allocated on the heap, so i dont immediately see a reason why the value of that pointer isnt just 0 and not 2.1305e-313 or so...

ps: even when i place the timer in my mainwindow and delete the updateProgressBar class, the value gets changed :/

wysota
17th April 2009, 18:14
I don't understand why you are passing that pointer to double around.

aloha
17th April 2009, 18:36
the filtering is done in my library, i'm running this operation through the command
ds->Filter(...);
how else can I know how much computation is already completed without passing a pointer?
The filteroperation is a function, not an object, so i cant have a public member function or so.
During the filter process i can update that status (pointer to double) and in my gui i have a timer who checks the value of that pointer every 0.2 seconds.

I need to have access to the same value from two sections of my application.
1) Write access for the filterfunction, to update the status of the progress
2) read access for the gui to read the value of the current progress

Don't think there is another decent/better way to accomplish this

wysota
18th April 2009, 23:11
how else can I know how much computation is already completed without passing a pointer?
That's what the setValue() method in the progress bar is for, isn't it?


The filteroperation is a function, not an object, so i cant have a public member function or so.
During the filter process i can update that status (pointer to double) and in my gui i have a timer who checks the value of that pointer every 0.2 seconds.

Let's temporarily forget accessing a shared variable from within multiple threads is WRONG... Updating the variable (why is it created on heap anyway?) won't do you any good because you have to inform the environment that you have actually changed it. You try to get away from it by wasting computer cycles with the timer but it's a bad approach. You should say "hey, my progress value has changed to 3.14!" instead. You can do that either by emitting a signal or by posting an event that carries the value (the latter is probably better in your case as you can do that from an arbitrary point in your code).


Don't think there is another decent/better way to accomplish this

I hate to be rude but the one you picked is far from being decent. What happens if you read the value from one thread while it is being overwritten by the other thead? You'll get a bogus value. In your case it's not that dangerous, you'll just get a wrong progress value for a while but in a general case this can blow up the world.

aloha
19th April 2009, 16:29
That's what the setValue() method in the progress bar is for, isn't it?



Let's temporarily forget accessing a shared variable from within multiple threads is WRONG... Updating the variable (why is it created on heap anyway?) won't do you any good because you have to inform the environment that you have actually changed it. You try to get away from it by wasting computer cycles with the timer but it's a bad approach. You should say "hey, my progress value has changed to 3.14!" instead. You can do that either by emitting a signal or by posting an event that carries the value (the latter is probably better in your case as you can do that from an arbitrary point in your code).



I hate to be rude but the one you picked is far from being decent. What happens if you read the value from one thread while it is being overwritten by the other thead? You'll get a bogus value. In your case it's not that dangerous, you'll just get a wrong progress value for a while but in a general case this can blow up the world.

Ok , let's start again, i've got no threads. I've got a mainwindow that starts a filterfunction. Now i want to add a progressbar reporting the progress of the filteroperation.
The filterfunction is in another file. how can i have access to the state of the progress of the filterfunction? My suggestion was to pass an integer/double by reference/pointer that I update during the filter progress. For example, i filter 5 different things, so the value of that integer/double will be incremeted every time by 20 (0%, 20%, 40% .... 100%)

Now, to update the progressbar on the screen, i need a timer to check like every 0.2 seconds the value of that passed integer/double. That's why i need the QTimer.
And i use the QTimerEvent to know when the timer has timed out and when to update the value on the screen.

If you can tell me another way how to check on the status/progress of a procedure without passing an integer/double that holds the status, please do...

wysota
23rd April 2009, 21:07
Use signals and slots.