Related
I created a QMessageBox instance with single button "OK" in slot connected to QTimer signal timeout() and displayed it using exec function and it seems that timer halts until button pressed and the box closed. I expected exec to create local event loop and dispatching timer messages, nevertheless timer was halted (no signal timeout() emited). Can anyone explain? P.S Sorry for my english.
UPDATE:
sample code:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QTimer *tm1=new QTimer(this),
*tm2=new QTimer(this);
connect(tm1,SIGNAL(timeout()),this,SLOT(tslot1()));
connect(tm2,SIGNAL(timeout()),this,SLOT(tslot2()));
tm1->start(1000);
tm2->start(1000);
}
void MainWindow::tslot1(void)
{
static int cnt;
qWarning(QString("slot 1 called %1 time(s)").arg(++cnt).toAscii().data());
}
void MainWindow::tslot2(void)
{
static int cnt;
qWarning(QString("slot 2 called %1 time(s)").arg(++cnt).toAscii().data());
if (3==cnt)
{
QMessageBox *mb=new QMessageBox(QMessageBox::Critical,tr("Error"),tr("tm2 halted !"),QMessageBox::Ok,this);
mb->exec();
}
}
MainWindow::~MainWindow()
{
delete ui;
}
Well, you can see that event loop is running, because timer1 is firing timeouts. From documentation you can see that exec() is blocking call - so it will block the caller function (QTimer slot) until dialog is closed.
That leads us to the QObject source code. After executing slot function, in QMetaObject::activate, last step is to reset the sender of the signal:
QObjectPrivate::resetCurrentSender(receiver, ¤tSender, previousSender);
This will reset QTimer state, and it will start countdown again. I think this is the common-sense approach to ensure that timer callbacks are called sequentially - like timers in most UI libraries, and not in parallel within the same timer.
Because exec() will block timer callback, QTimer will not be reset and callback will not be called again (until function resumes, and QTimer gets its reset).
You have several ways in handling this:
1) If you don't want your function to block, and you don't need result of modal dialog to proceed with the function - call QMessageBox::open() instead of QMessageBox::exec() - it will not block the caller function.
2) If you need result of the dialog to continue the function, and you don't want to stop your timer callbacks, naive approach is to reset QTimer manually by calling QTimer::stop() and QTimer::start() sequentially when entering the callback function.
I'm new to Qt, and want to simply display a video in Qt GUI. I basically got everything figured out, except for some details handling the QThread, which is really annoying.
I reformulate my question into a simpler program, hope it will explains better
first I define this QObject class
#include <QObject>
#include <QDebug>
class myObject : public QObject
{
Q_OBJECT
public:
explicit myObject(QObject *parent = 0);
bool stop;
signals:
void finishWork();
public slots:
void dowork();
void onfinishThread();
};
myObject::myObject(QObject *parent) :
QObject(parent)
{
stop = true;
}
void myObject::dowork(){
qDebug()<<"start working!";
while(!stop){
qDebug()<<"working...";
}
emit finishWork();
qDebug()<<"finish do work!";
}
void myObject::onfinishThread(){
qDebug()<<"thread is finished!";
}
then the main function
#include <QCoreApplication>
#include <QThread>
#include <iostream>
#include <windows.h>
#include "myobject.h"
using namespace std;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
myObject* myObj = new myObject();
QThread* myThread = new QThread;
myThread->connect(myObj, SIGNAL(finishWork()), SLOT(quit()));
myObj->connect(myThread, SIGNAL(started()), SLOT(dowork()));
myObj->connect(myThread, SIGNAL(finished()), SLOT(onfinishThread()));
myObj->moveToThread(myThread);
myObj->stop = false;
myThread->start();
cout<<"Press ENTER to continue....."<<endl<<endl;
cin.ignore(1);
myObj->stop = true;
Sleep(10);
if(myThread->isRunning()){
qDebug()<<"The thread is still running?!!!";
}
/*
myObj->stop = false;
Sleep(1000);
myThread->start();
myObj->stop = true;
*/
myObj->deleteLater();
myThread->deleteLater();
return a.exec();
}
As you can see, I even used cin to try let the dowork() run first, but it didn't work at all, the output is
so I'm really confused on how scheduling works for QThread...
Also, if you uncomment the part
/*
myObj->stop = false;
Sleep(1000);
myThread->start();
myObj->stop = true;
*/
the output is exactly the same! only stays a while after printing
The thread is still running?!!!
Would anyone help me with this? Thanks a lot. You may simply copy all the code and test it yourself.
-------------------------Original Question, bad explanation, please ignore....----------------------------------------
I made a videoObj Class with only one function to Query the frames, the function is defined as:
void videoObj::ProcessFrame(){
bool getframe;
qDebug()<<"get in ProcessFrame";
while(!stop_flag){
getframe = capture_.read(imgOriginal_);
if(!getframe){
qDebug()<<"Video End!";
break;
}
cv::cvtColor(imgOriginal_, imgOriginal_, CV_BGR2RGB);
QImage qimgOriginal((uchar*)imgOriginal_.data, imgOriginal_.cols, imgOriginal_.rows, imgOriginal_.step, QImage::Format_RGB888);
emit imgProcessed(qimgOriginal);
this->thread()->msleep(10);
//qDebug()<<"processing frames";
}
emit stopProcess();
qDebug()<<"Stop thread";
}
Basically above code is just query frames and whenever got one emit the
SIGNAL imgProcessed(qimgOriginal)
and whenever the stop_flag is set on, stop the while loop and emit the
SIGNAL stopProcess()
I use this class in the MainWindow Class, here is how I define the connection in the constructor:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
video_obj_ = new videoObj();
video_thread_ = new QThread;
this->connect(video_obj_, SIGNAL(imgProcessed(QImage)), SLOT(onImgProcssed(QImage))); \\this is for displaying the image
video_obj_->connect(video_thread_, SIGNAL(started()), SLOT(ProcessFrame()));
video_obj_->moveToThread(video_thread_);
video_thread_->connect(video_obj_, SIGNAL(stopProcess()), SLOT(quit()));
}
Above code works fine in frame query. The problem I don't understand is, if I set video_obj_->stop_flag on in any MainWiow member function, the ProcessFrame() in videoObj Class should emit stopProcess() signal and trigger quit() of video_thread_, and then the thread should finish, that is video_thread_->finished() is true.
However, if I do something like:
connect(video_thread_, SIGNAL(finished()), this, SLOT(onStopProcess())); //onStopProcess() see below
void MainWindow::on_btnStart_clicked()
{
video_obj_->stop_flag = 1;
this->thread()->msleep(10);
video_obj_->capture_.open(ui->lineEditVidoeAddress->text().toStdString());
//... something about videoCapture setting here, not related
video_obj_->capture_.set(CV_CAP_PROP_POS_FRAMES, 0);
video_obj_->stop_flag = 0;
this->thread()->msleep(10);
if(video_thread_->isRunning()){
qDebug()<<"The thread is still running?!!!";
}
video_thread_->start();
}
void MainWindow::onStopProcess(){
qDebug()<<"thread is finished";
}
It will give me the output:
Stop thread
The thread is still running?!!!
thread is finished
Which means triggering the quit() is not finish the thread, or quit() has not been triggered.
If I use:
video_thread_->wait(); //instead of video_thread_->wait(10);
The program will just freeze.
Is anyone can help me with this, it really confuse me about this quit() and finished()...
Thanks!
Since when you call stop_flag=1, the video_thread_ finishes the current function which is
ProcessFrame
before ProcessFrame finish, quit is called on the video_thread_ through emit stopProcess().
However, quit is different from terminate, terminate can exit the thread any time (but it is not safe), quit works with the event loop, if the thread has no eventloop, quit has no effect.
I guess before qthread execute the next event in the eventloop, it checks some flag, which can be set by quit, it the flag is set by quit, then it won't execute the next event in the eventloop. Or it can also be that a quit event is inserted into the eventloop, and the next event in the eventloop will be quit.
After stop_flag = 1, you called video_thread_->wait, that will block the video_thread_ from executing the next event in the eventloop, thus the quit will not take effect before time out, however, the nextlines which print "not finished?!!!" is executed immediately. Instead of calling video_thread->wait, if you call currentThread()->Sleep(some_enough_time), then there will be time for the video_thread_ to execute the next event and quit.
You can read the Qt documentation of QThread, wait is used with terminate to terminate a thread synchronously.
============================== The new code ================================
When you do:
myObj->connect(myThread, SIGNAL(started()), SLOT(dowork()));
The signal source is myThread, the slot also belongs to myThread, since the object "myThread" is created in the main thread, thus as a object it lives in the main thread. You can call myThread->thread() to see this, it will return the main thread instead of the new thread.
However, the started signal is emitted from the new thread namely the thread that myThread represents, thus the connection is a Qt::QueuedConnection. dowork() is posted in the event queue of main thread, and it'll be executed only after a.exec() which executes the main threads eventloop.
The same thing happens to the other 2 connect calls, all the slots will be executed in the eventloop of the main thread.
First the start is emitted from the new thread when myThread->start is called, dowork is posted in the main thread's event queue.
Nothing really happens before you call a.exec();
So the program will go ahead to cin and then set stop to true, and then print "Thread is still running?!!".
Second When a.exec() is called, the dowork is executed in the main thread and "start work" is printed. Nothing is done because the stop flag is already true, and the finishwork signal is emitted from the main thread, print "finish do work";
Third The last step the finishwork is emitted, and the quit slot is directly called. However, before the new thread can really quit, the main thread has already finished the eventqueue, because no more events are posted to the main thread. The application exits without waiting for quit() to take effect.
To test this is true, do not modify any of your code, just add after
emit finishWork();
currentThread()->sleep(1000);
you will see "thread is finished!" printed, because this make time for the new thread to emit finished(), and the onfinishThread() will be add to the main thread's eventqueue.
BTW, your way of working with thread looks like java style, which is not the standard qt way. You can read this before you work on qt thread.
This is not a scheduling issue.
That you did in your code looks like:
Create a thread.
Then this thread emits a signal that it started, run slot dowork()
start a thread.
Wait for user input
echo about thread is running
execute event loop
At point 3 thread is already running and signalled about that. Because myObj is created in main thread and not moved to any other thread (to process events there), your thread does not do anything else now, but just spins event loop. At the same time event that tells you want to run myObj's dowork() is posted on the main thread. At last then it comes to step 6 you start to execute event loop of it, and first thing it finds is event that it needs to call dowork() on myObj.
To make it clear to you how Qt threads and signal-slot connection works, I recommend you to read this article on Qt blog.
In simple to fix it, you can move your object myObj to the thread that you wan't to run.
But to make this really correct, I bet that you really want is to subclass QRunnable and (re-)implement it's run method to do the stuff you wan't in QThread, so that thread does it's job, than finished correctly, so you can join on it. Or depending on your goal you might be even better with using QtConcurrent::run(aFunction)
I am using Opencv for some real-time video processing.
As a front-end I am using QT framework.
On my GUI, I have an input image window (mapped to a Label) and an output image window (mapped to another Label) and 3 push buttons. One to Start input video capture, the second to process the video (code not written yet), and third to Exit.
I am currently able to stream my video and display it on the Front-end. But this locks my GUI and am unable to Exit.
I tried using QTimers (using suggestions from this and the QT forum), but my GUI still remains locked.
Would appreciate if someone can point my in the right direction.
Below is the code:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp> // for cvtColor
#include <iostream>
#include <QTimer>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_buttonCaptureVideo_clicked();
void on_buttonExit_clicked();
public slots:
virtual void doNextFrame() {repaint();}
private:
Ui::MainWindow *ui;
CvCapture *capture; // OpenCV Video Capture Variable
IplImage *frame; // Variable to capture a frame of the input video
cv::Mat source_image; // Variable pointing to the same input frame
cv::Mat dest_image; // Variable to output a frame of the processed video
QTimer *imageTimer;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
cvReleaseImage(&frame);
cvReleaseCapture(&capture);
}
void MainWindow::on_buttonCaptureVideo_clicked()
{
// Set to 25 frames per second
const int imagePeriod = 1000/25; // ms
imageTimer = new QTimer(this);
imageTimer->setInterval(imagePeriod);
connect(imageTimer, SIGNAL(timeout()), this, SLOT(doNextFrame()));
// Use the default camera
capture = cvCreateCameraCapture(-1);
while(capture)
{
// Capture a frame
frame = cvQueryFrame(capture);
// Point to the same frame
source_image = frame;
// Resize Image
cv::resize(source_image, source_image, cv::Size(128,128) , 0, 0);
// Change to RGB format
cv::cvtColor(source_image,source_image,CV_BGR2RGB);
// Convert to QImage
QImage qimg = QImage((const unsigned char*) source_image.data, source_image.cols, source_image.rows, QImage::Format_RGB888); // convert to QImage
// Display on Input Label
ui->labelInputVideo->setPixmap(QPixmap::fromImage(qimg));
// Resize the label to fit the image
ui->labelInputVideo->resize(ui->labelInputVideo->pixmap()->size());
}
}
void MainWindow::on_buttonExit_clicked()
{
connect(ui->buttonExit, SIGNAL(clicked()), qApp, SLOT(closeAllWindows()));
}
When you click your button, the
while(capture) { ... }
loop will run forever, as capture will never be set to NULL.
This means the code flow never leaves your loop and thus the main thread cannot process anything else, like e.g. repainting.
The QTimer will emit its timeout() signals, but they will be placed as Events in Qt's Event Queue. As long as your on_buttonCaptureVideo_clicked() method is running, those Events will not be processed.
Here are my suggestions how to make it work:
This code:
// Set to 25 frames per second
const int imagePeriod = 1000/25; // ms
imageTimer = new QTimer(this);
imageTimer->setInterval(imagePeriod);
connect(imageTimer, SIGNAL(timeout()), this, SLOT(doNextFrame()));
// Use the default camera
capture = cvCreateCameraCapture(-1);
belongs into the constructor of MainWindow as you want to set that up one time only. There is no need to do it again when the user clicks the button the second, third, etc time.
The code which is within your while loop should go into the doNextFrame() slot (without the while construct).
Then your button will only do
imageTimer->start();
and then e.g. do
imageTimer->stop();
when it is clicked again.
Example code:
void MainWindow::on_buttonCaptureVideo_clicked()
{
if( imageTimer->isActive() )
{
imageTimer->stop();
}
else
{
imageTimer->start();
}
}
What will happen if you do that?
When you click the button, your on_buttonCaptureVideo_clicked() clicked slot will be called from the GUI thread, the timer will be started, and the method will return almost instantly.
Now the GUI thread is free and able to handle repaints etc.
From that point on, the timer will be sending timeout() signals every 40ms. Whenever the GUI thread is free, it will handle this signal and call your doNextFrame slot.
This slot will capture the next frame and return when it is done. When it is done, the GUI thread will be able to process other Events (e.g. repaint) again.
As soon as you click the button again, the timer will stop, and no new timeout() events will be sent. If you still see a couple of frames after the button has been clicked, this could mean that the timer events were sent faster than they could be processed.
Preface: I am not strong in C++ so I cannot offer specific code, but I am experienced in PyQt
This is a common pitfall for newcomers to Qt. What it seems your on_buttonCaptureVideo_clicked is doing is entering into a loop in your main GUI thread to do work. In QT, you want to avoid doing anything busy in your main thread. The Qt eventloop needs to be able to constantly process and flush your GUI events as they come in. What you are doing is blocking the event loop.
There are two different things you can do here. The first is the more basic approach but allows you to see more immediate results. You can "pump" the eventloop. Depending on how fast your while loop iterates, you can call qApp->processEvents();. This will allow Qt to process the pending GUI events and make your app appear more responsive. Its basically sharing time between your while loop and the main loop. Maybe you want to call this on every nth frame. Depends how often you want to make sure the GUI refreshes.
The other option, which is more preferable, is to place your capture loop into a QThread. When a new frame is available you can emit a signal with the frame data. The signal will get placed into the Qt event loop to be processed with everything else and will not hold up your GUI. Generally this is the approach you want to take with any heavy crunching or long running callables.
Edit
I just realized that you start a QTimer in addition to doing a loop in the main thread. If you want to use a QTimer, and your image processing is not too heavy (it doesn't take long per cycle) then you should move everything into the doNextFrame and completely remove the while loop. If your doNextFrame is a heavy process, then you should be using a QThread and signals.
For the last few days, I've been trying out the new preferred approach for using QThreads without subclassing QThread. The trouble I'm having is when I try to shutdown a set of threads that I created. I regularly get a "Destroyed while thread is still running" message (if I'm running in Debug mode, I also get a Segmentation Fault dialog). My code is very simple, and I've tried to follow the examples that I've been able to find on the internet.
My basic setup is as follows: I've a simple class that I want to run in a separate thread; in fact, I want to run 5 instances of this class, each in a separate thread. I have a simple dialog with a button to start each thread, and a button to stop each thread (10 buttons). When I click one of the "start" buttons, a new instance of the test class is created, a new QThread is created, a movetothread is called to get the test class object to the thread...also, since I have a couple of other members in the test class that need to move to the thread, I call movetothread a few additional times with these other items. Note that one of these items is a QUdpSocket, and although this may not make sense, I wanted to make sure that sockets could be moved to a separate thread in this fashion...I haven't tested the use of the socket in the thread at this point.
Starting of the threads all seem to work fine. When I use the linux top command to see if the threads are created and running, they show up as expected.
The problem occurs when I begin stopping the threads. I randomly (or it appears to be random) get the error described above.
Class that is to run in separate thread:
// Declaration
class TestClass : public QObject
{
Q_OBJECT
public:
explicit TestClass(QObject *parent = 0);
QTimer m_workTimer;
QUdpSocket m_socket;
Q_SIGNALS:
void finished();
public Q_SLOTS:
void start();
void stop();
void doWork();
};
// Implementation
TestClass::TestClass(QObject *parent) :
QObject(parent)
{
}
void TestClass::start()
{
connect(&m_workTimer, SIGNAL(timeout()),this,SLOT(doWork()));
m_workTimer.start(50);
}
void TestClass::stop()
{
m_workTimer.stop();
emit finished();
}
void TestClass::doWork()
{
int j;
for(int i = 0; i<10000; i++)
{
j = i;
}
}
Inside my main app, code called to start the first thread (similar code exists for each of the other threads):
mp_thread1 = new QThread();
mp_testClass1 = new TestClass();
mp_testClass1->moveToThread(mp_thread1);
mp_testClass1->m_socket.moveToThread(mp_thread1);
mp_testClass1->m_workTimer.moveToThread(mp_thread1);
connect(mp_thread1, SIGNAL(started()), mp_testClass1, SLOT(start()));
connect(mp_testClass1, SIGNAL(finished()), mp_thread1, SLOT(quit()));
connect(mp_testClass1, SIGNAL(finished()), mp_testClass1, SLOT(deleteLater()));
connect(mp_testClass1, SIGNAL(finished()), mp_thread1, SLOT(deleteLater()));
connect(this,SIGNAL(stop1()),mp_testClass1,SLOT(stop()));
mp_thread1->start();
Also inside my main app, this code is called when a stop button is clicked for a specific thread (in this case thread 1):
emit stop1();
Sometimes it appears that threads are stopped and destroyed without issue. Other times, I get the error described above.
Any guidance would be greatly appreciated.
Thanks,
Bryan
http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
"Finally, to prevent nasty crashes because the thread hasn’t fully shut down yet when it is deleted, we connect the finished() of the thread (not the worker!) to its own deleteLater() slot. This will cause the thread to be deleted only after it has fully shut down."
Please try replacing:
connect(mp_testClass1, SIGNAL(finished()), mp_thread1, SLOT(deleteLater()));
with:
connect(mp_thread1, SIGNAL(finished()), mp_thread1, SLOT(deleteLater()));
Please refer to the following link as I tried to simplify the problem I was having and now have run into a problem that I cannot solve.
Link: Qt: How to use QTimer to print a message to a QTextBrowser every 10 seconds?
In the posting for the link above I simplified the task I am trying to do by simply saying that I wanted to push a button and have it display something in a QTextBrowser every 10 seconds. I was having trouble getting QTimer working at the time so I thought that if I could get QTimer to work then I could finish my task.
What I am really trying to do is read lines from a file and after every 2500 lines I would like to print a message and then wait 10 seconds.
Pseudocode:
while(not at the end of the file)
{
read in 2500 lines
print a message
wait 10 seconds
}
QTimer is nice but it does opposite of what I want it to do. Instead of transmitting a message and waiting 10 seconds, first they wait 10 seconds, timeout and then send the message.
So to get it to work the way I want I first called the printMessage() SLOT and then I made a SLOT called stopTimer() that simply stops the timer. So after the 10 seconds has passed it will simply call stopTimer() and then continue processing the input file.
Onto the REAL problem:
Qt does not wait for a QTimer to finish before it moves on through the code. I want the code to wait the full 10 seconds before it reads the next 2500 lines of code. I found that QTimer has an isActive() function that returns a bool value.
So in the place where I wanted the 10 second delay to finish I put the following:
while(timer->isActive());
I figured the program would stay in this loop for 10 seconds and then exit after the QTimer timed out because the condition would then be false. The problem is that it doesn’t exit the loop because the status of the timer never changes regardless of how long it waits in this loop! I checked with the debugger and isActive( ) remains true regardless of elapsed time.
I then omitted the while(timer->isActive()) loop and observed the timer in the debugger. It seems that the timer does not actually start timing until it exits the while(not at the end of the file). So I believe that since the while(timer->isActive()) loop is nested inside this, it is causing it to never timeout. I could be wrong but this is what seems like is happening. Also, what is annoying is that the QTimer object has no field that shows the elapsed time of the timer when it is active. Therefore, I cannot check the elapsed time at all to further debug.
Someone please test this out or let me know a workaround for this!
For something that sounds so easy, this has been the biggest pain I have had in recent time, but I generally don’t use Qt so it could be my lack of experience.
Here is an excerpt from code I have which currently freezes as stated above:
void Form::startBtn_pushed()
{
QTimer *timer = new QTimer(this);
QFile file(“file.txt”);
QTextStream stream(&file);
QString line;
int lineCount = 0;
connect(timer, SIGNAL(timeout()), this, SLOT(stopTimer()));
while(!(stream.atEnd())
{
line = stream.readLine();
lineCount++;
if(lineCount == 2500)
{
printMessage();
timer->start(10000);
while(timer->isActive()); //Wait for timer to timeout before continuing
}
}
}
Sorry for the long post and if I might have made any grammatical errors with my code here. I do not have internet access on my dev machine so I had to retype this here.
Doing something like while(timer.isActive()) is not a good idea at all as it will cause your application to consume around 100% CPU time. It will also cause your application to never return to the event processing loop where the actual code for timer is executed, that's why it freezes.
If you still want to use this approach, you should call QCoreApplication::processEvents() in the loop. It will temporarily pass control back to the event loop, so it will cause timer to time out. Instead of connecting timeout() to stopTimer(), you can just call timer.setSingleShot(true) before you start it, it will cause it to stop automatically after the first timeout.
Note that you have a memory leak there as you create a new timer on each button push. Surely they are children of your form and will be destroyed, but only when the form is destroyed.
If you want a more elegant approach, you can create a separate class for reading that file. In the constructor you'd open your file and stream which should be fields in this class. This class should also have a sort of readMore() slot which will read 2500 lines, then put a message and return. If it doesn't reach the end of the stream, then it would call QTimer::singleShot(10000, this, SLOT(readMore())), which will cause the event loop to call readMore() again in 10 seconds. The code would looks something like this (didn't check for errors):
// myfilereader.h
class Form;
class MyFileReader: public QObject {
Q_OBJECT
public:
MyFileReader(const QString &fileName);
// this should be called after you create an instance of MyFileReader
void startReading() {readMore();}
private:
QFile file;
QTextStream stream;
private slots:
void readMore();
signals:
void message(); // this should be connected to printMessage() in the Form
void finished();
};
// myfilereader.cpp
MyFileReader::MyFileReader(const QString &fileName):
file(fileName),
stream(&file),
{
// open the file, possibly throwing an exception
// or setting some sort of "invalid" flag on failure
}
void MyFileReader::readMore()
{
QString line;
int lineCount = 0;
while(!(stream.atEnd())
{
line = stream.readLine();
lineCount++;
if(lineCount == 2500)
{
emit message();
break;
}
}
if (stream.atEnd())
emit finished();
else
QTimer::singleShot(10000, this, SLOT(readMore()));
}
This is a kind of more heavyweight approach, but this is the price of asynchronous event handling. You could also put all this stuff into the Form class, but I think using a separate class is better.
As Daniel points out, if reading 2500 lines takes a long time, say 5 seconds, the message will be printed after 10 seconds after the reading has finished, that is, 15 seconds after it has started. If you want the message to be printed approximately each 10 seconds no matter how long reading takes, you should add a QTimer timer field to the class, connect it's timeout() signal to the readMore() slot in the MyFileReader constructor, then in the startReading() method call timer.start() before calling readMore(). Now, at the end of readMore() do this:
if (stream.atEnd()) {
timer.stop();
emit finished();
}
You need a QTimer field in this case because you can't cancel a QTimer::singleShot() call, but you need to do it if you have reached the end of the stream, otherwise your readMore() will just keep on getting called again and again, even if there is nothing more to read. Note that even in this case, it is still possible for the message to appear less frequently than every 10 seconds in case if reading 2500 lines takes longer than these 10 seconds. If you want exactly 10 seconds, you should probably check the elapsed time in the loop instead, but I think that's an overkill, unless you expect reading to be very slow.
Slightly off topic, but if you want an easy way to avoid memory leaks, you can also do this in the constructor:
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
It will automatically mark your reader for deletion when you emit finished(), and, once that happens, the reader will be deleted as soon as the control goes back to the event loop. That is, after all slots connected to the finished() signal return. This approach allows you to just allocate a MyFileReader on the heap, then discard the pointer without worrying about memory leaks.
QTimer has a signal called timedout(). If you connect a slot to this, you can set the initial timer to a REALLY short interval (1 MS maybe). When the timer expires, inside the slot, you can send your message. At the end of the slot, set the interval to 10 seconds, set singleshot to false, and the start the timer again.
If you don't want to do the setting each and every time the slot is called, you can simply make 2 slots. The first time, the slot does the setup for the rest of the calls. It also disconnects itself from the QTimer and connects the second slot. Then things continue merrily on their way.
Edit:
Also, realize that when slots are called they are called on the event thread. So, by clicking a button and putting a loop that spin-blocks/busy-waits until the timer expires, you are guaranteeing that you will not enter this state because you are blocking the very thread that the timeout signal will be processed on.
QTimer does all of its processing inside Qt's event loop, so your code must return to the event loop to cause the timer to time out. So what you want to do is have startBtn_pushed set up the timer, connect a slot to the timer's timeout signal, then probably call that slot itself (so that it is called immediately). It would look something like this:
// timer, file, and stream are now instance variables (or maybe file and stream
// are broken out into their own class... up to you.
void Form::startBtn_pushed()
{
// timer has been allocated before (but not started)
file.open(“file.txt”);
stream.setDevice(&file);
connect(timer, SIGNAL(timeout()), this, SLOT(readAndPrint()));
timer->start(10000);
readAndPrint(); // respond to the button press immediately
}
void Form::readAndPrint()
{
int lineCount = 0;
while(!(stream.atEnd() && lineCount < 2500)
{
line = stream.readLine();
lineCount++;
if(lineCount == 2500)
{
printMessage();
}
}
}
I have figured out a solution that is much simpler than those proposed here.
I realized it was much easier to use a QTime object instead of a QTimer object.
Basically you start a QTime object and then you use its elapsed() function to check how much time has passed to since it was started.
Thanks to everyone for taking the time to help. I did try to implement a couple of your solutions into my code but had some trouble since I am not a pro at Qt and they were more complex than I would have thought. In the end I find this solution to be a easy solution to what should have been an easy problem.
I certainly learned a lot from this problem and I hope you all did too.
My question to you is why didn't anyone suggest using QTime from the start?
Thanks again!
My solution:
void Form::startBtn_pushed()
{
QTime *timer = new QTime();
QFile file(“file.txt”);
QTextStream stream(&file);
QString line;
int lineCount = 0;
connect(timer, SIGNAL(timeout()), this, SLOT(stopTimer()));
while(!(stream.atEnd())
{
line = stream.readLine();
lineCount++;
if(lineCount == 2500)
{
timer->start();
while(1)
{
QApplication::processEvents();
if(timer->elapsed() == 10000)
{
printMessage();
break;
}
}
}
}
}
QEventLoop l;
connect( timer, SIGNAL( timeout() ), &l, SLOT( quit() ) );
l.exec(); // Waiting without freezing ui