Test method with Signals and Slots - c++

I have the following two simple methods in a Qt Console Application:
void Webservice::getFile(PostElement el)
{
parameter = ⪙
QUrl url(PATH);
QUrlQuery query;
query.addQueryItem(el.getParam(), el.getValue());
url.setQuery(query);
request.setUrl(url);
manager->get(request);
connect(manager, SIGNAL(finished(QNetworkReply*)), this,
SLOT(downloadCompleted(QNetworkReply*)));
}
void Webservice::downloadCompleted(QNetworkReply *reply)
{
QByteArray b = reply->readAll();
qDebug() << "Download completed!";
QFile file("/tmp/" + parameter->getValue());
file.open(QIODevice::WriteOnly);
file.write(b);
file.close();
reply->deleteLater();
}
I created a QTest project to test getFile method.
#include <QtTest>
#include <QCoreApplication>
// add necessary includes here
#include "webservice.h"
class WebserviceTest : public QObject
{
Q_OBJECT
public:
WebserviceTest();
~WebserviceTest();
private:
Webservice ws;
private slots:
void initTestCase();
void cleanupTestCase();
void getFile();
};
WebserviceTest::WebserviceTest()
{
}
WebserviceTest::~WebserviceTest()
{
}
void WebserviceTest::initTestCase()
{
}
void WebserviceTest::cleanupTestCase()
{
}
void WebserviceTest::getFile()
{
PostElement el{"file", "myPic.png"};
ws.getFile(el); // it should wait for the slot to be executed
}
QTEST_MAIN(WebserviceTest)
#include "tst_webservicetest.moc"
As you can see, it has signals and slots. If I run that one on my original project, I see that downloadCompleted executes without issues (because it is using the Event Loop). But in the test project the slot is not called. I googled and found some examples using QSignalSpy, but at the moment I have not executed the slot successfully.
How to tell my test to "wait" for the slot to be completed?

That's exactly the purpose of QSignalSpy, it basically spins new QEventLoop that waits for signal to be executed. Your tests starts and asynchronous operation but it doesn't wait for it to finish so it will destruct objects while the operation is ongoing.
Qt test also has some built-in timeouts that prevents tests to be stuck which are stopped when QSignalSpy is used. In your case you need to emit new signal from your class Webservice indicating that download is finished, e.g. (you can also pass arguments to the signal) if needed:
class Webservice : public QObject {
Q_OBJECT
// Constructors etc.
signals:
void finished();
}
Then at the end of your void Webservice::downloadCompleted(QNetworkReply *reply):
emit finished();
and in your test something like this:
PostElement el{"file", "myPic.png"};
// Prepare spy
QSignalSpy spy(&ws, SIGNAL(finished));
ws.getFile(el);
// Wait for result - by default this waits 5 seconds
spy.wait();
// Check if we had a timeout or actually signal was raised
QCOMPARE(spy.count(), 1);

Related

Qt moveToThread, signals/slots with arguments

I've tried to use an approach from https://wiki.qt.io/QThreads_general_usage
with moveToThread. Everything is fine. But if I try to add the argument to the finished signal, there is the following problem:
class Worker : public QObject {
Q_OBJECT
public:
Worker();
~Worker();
public slots:
void process();
signals:
void finished(const std::string& value);
};
void Worker::process() { // Process. Start processing data.
// allocate resources using new here
qDebug("Hello World!");
std::string s = someFunctionReturningString();
emit finished(s);
}
The main class is:
class Main: public QObject {
Q_OBJECT
public:
void startProgram();
public slots:
void slotFinished(const std::string& s);
};
void Main::startProgram() {
QThread* thread = new QThread;
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(thread, &QThread::started, worker, &Worker::process);
connect(worker, &Worker::finished, thread, &QThread::quit);
connect(worker, &Worker::finished, worker, &Worker::deleteLater);
connect(worker, &Worker::finished, this, &Main::slotFinished);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
void Main::slotFinished(const std::string& value) {
qDebug() << "result " << value.c_str();
}
If I connect this finished signal to some slot (slotFinished), I didn't get the call of this slot.
Is it an expected behavior of the signals/slots/moveToThread?
Problem is meta data information.
When you do a default connection between signal and slot and when signal is emitted from different thread than receiver is assigned to, Qt does a magic and wraps arguments for signal (creates a copy) and queue them in event loop of destination thread.
Then when destination tread executes logic of the Qt event loop, values are unwrapped and respective slot is invoked with copied values.
Now to be able to do this copy, Qt has to have some minimal knowledge about that type.
So when you use Qt types it will work out of the box, but you use external types like std::string in your case, you have to first perform type registration.
So basically your code is missing something like this:
// to be declared somewhere
Q_DECLARE_METATYPE(std::string);
// to be invoked at the beginning of program
qRegisterMetaType<std::string>();
Without type registration Qt doesn't know how to do a copy and provides a warning in logs. Check Qt logs I'm sure it prompts you with proper error message.

( QNativeSocketEngine) QObject: Cannot create children for a parent that is in a different thread

I'm having a problem which was ask here for a similar situation a couple of times, however I couldn't find the solution in those topics.
I'm having a main class where I'd like to extend it with a qt-network support, and this by an additional class. Let me break the source code down for you to the relevant parts:
main class
.h
class MainClass: public QObject{
Q_OBJECT
public:
[...]
private:
NetworkSupport * netSupport;
};
.cpp
MainClass::MainClass()
{
[...]
netSupport
netSupport = new NetworkSupport(this->thread());
netSupport->start();
[...]
}
network class
.h
class NetworkSupport : public QThread
{
Q_OBJECT
public:
NetworkSupport(QThread *mainThread);
~NetworkSupport();
QByteArray readData();
void sendData(QByteArray *msg);
public slots:
void acceptConnection();
void receive();
void run();
private:
QByteArray curMessage;
QThread* libThread;
QEventLoop initWlan;
protected:
QTcpServer server;
QTcpSocket* client;
};
.cpp
NetworkSupport::NetworkSupport(QThread* mainThread)
{
libThread = mainThread;
server.moveToThreaD(libThread);
server.listen(QHostAddress::Any, 5200);
QMetaObject::invokeMethode(&initWlan, "quit", Qt:QueuedConnection);
initWlan.exec();
}
void NetworkSupport::run(){
connect(&server, SIGNAL(newConnection()), this, SLOT(acceptConnection()));
}
void NetworkSupport::acceptConnection()
{
client = server.nextPendingConnection();
client->moveToThread(libThread);
if (client->state() == QAbstractSocket::ConnectedState)
connect(client, SIGNAL(readyRead()), this, SLOT(receive()));
}
void NetworkSupport::receive()
{
curMessage = client->readAll();
}
void NetworkSupport::sendData(QByteArray* msg)
{
if(client->state() == QAbstractSocket::ConnectedState)
{
client->moveToThread(libThread);
printf("Sending a message %s\n",msg->data());
client->write(*msg);
client->waitForBytesWritten();
}
}
I know I usually don't need to specifiy the moveToThread() this much, but it doesn't change a thing in the end, if all of them are there or removed.
However when executing I get the error message at client->write(*msg) in sendData(), that is:
[...]
Sending a message ?r
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QnativeSocketEngine(0x2afc660), parent's thread is QThread(0x1b59210), current thread is QThread(0x7f6a70026b60)
QSocketNotifier: Can only be used with threads started with QThread
[...]
It does look like a warning, since the the program is still in execution afterwards, but I don't receive any messages from the client (i.e. receive() gets never executed), which is I guess because of the last line from the error message.
Could it be that this error message is just misleading and if so what's it actual meaning, or did I do something completely wrong?
You are doing so many things wrong in this code that I'm not sure what to start with.
NetworkSupport::NetworkSupport(QThread* mainThread)
{
libThread = mainThread;
server.moveToThreaD(libThread);
This will do nothing. server is already in same thread as MainClass instance
server.listen(QHostAddress::Any, 5200);
Server will start to listen on same thread as MainClass
QMetaObject::invokeMethode(&initWlan, "quit", Qt:QueuedConnection);
initWlan.exec();
This mocks me a lot. This will simply start event loop and quit it almost immedietly.
}
void NetworkSupport::run(){
connect(&server, SIGNAL(newConnection()), this, SLOT(acceptConnection()));
}
This will simply run in new thread, call connect and quit thread right after connect statement - your thread will not be running anymore after connect. Also slot acceptConnection will be probably called in same thread as MainClass. I wonder when and where is MainClass created.
Seems for me like you're struggling with to many things at same time. You probably read somwhere, that you should use separate thread for network communicaton, so you won't block other threads. Try single thread approach first. When you get that working, then you should think how to utilize other thread for what you need.
Bonus question: Is this code some kind of plugin for application that may not have Qt eventloop at all? Or is it part of the full-stack Qt application?
There are a few misunderstandings in your code how Qt networking and multi threading works.
First, in Qt docs they say on QTcpServer::nextPendingConnection():
The returned QTcpSocket object cannot be used from another thread. If
you want to use an incoming connection from another thread, you need
to override incomingConnection().
So, you must create your own Server class, that inherits from the QTcpServer, and override incomingConnection() there. In that overriden method you have to post the socket descriptor to other thread where your TCP client will live processing that connection. In this simple example we just emit a signal.
signals:
void myNewConnectionSignal(DescriptorType);
protected:
void incomingConnection(DescriptorType descriptor)
{
emit myNewConnectionSignal(descriptor);
}
Second, what is NetworkSupport thread for? I guess, you want your server object to live and work there? If so then you must rewrite it in other way. Below is the server part only. Note, that QThread already has a QEventLoop and you can use it via exec() in your run().
...
protected:
MyServer *server;
...
NetworkSupport::NetworkSupport()
{
// this is called in the main thread, so "this" lives in main thread
server=0;
client=0;
// say thread to start, but it will be actually started within run()
start();
// sometimes it is recommended to wait here for the thread started
}
NetworkSupport::~NetworkSupport()
{
// this is called in the main thread, say this thread to stop running
quit();
// wait for completion
wait();
}
void NetworkSupport::run()
{
// this is called in server thread
server=new MyServer();
if (server->listen(QHostAddress::Any, 5200))
{
// connect our new signal to slot where we create and init the TCP client, note that Qt::QueuedConnection is used because we want acceptConnection to run in the main thread
connect(server, SIGNAL(myNewConnectionSignal(DescriptorType)), MyClientsPool, SLOT(acceptConnection(DescriptorType)), Qt::QueuedConnection);
// go for infinite event loop
exec();
}
else
{
// do you always ignore errors?
}
// the thread is stopped, let's destroy the server in the thread where it was born
delete server;
server=0;
}
Client is living and working in your main thread. You must not call its methods directly from other threads. NetworkSupport lives in the main thread as well: yes, it incapsulates and manages some other thread but as an QObject itself it lives in the thread we created it in. The methods below are always executed in the main thread because we connected server's signal to NetworkSupport::acceptConnection() using Qt::QueuedConnection which says to Qt that we want the slot to be invoked in the thread where its QObject lives.
private slots:
void socketDisconnected();
...
void NetworkSupport::socketDisconnected()
{
if (client)
{
client->deleteLater();
client=0;
}
}
void NetworkSupport::acceptConnection(DescriptorType descriptor)
{
QTcpSocket* client=new QTcpSocket(this);
client->setSocketDescriptor(descriptor);
connect(client,SIGNAL(disconnected(),this,SLOT(socketDisconnected());
connect(client,SIGNAL(readyRead(),this,SLOT(receive());
}
void NetworkSupport::receive()
{
if (client)
curMessage = client->readAll();
}
void NetworkSupport::sendData(QByteArray* msg)
{
if (client)
{
client->write(*msg);
client->waitForBytesWritten();
}
}
UPDATE
If we just want to hide all network works inside the thread. Note, in the example below there is little error handling and a lot of messages data copies. You might want to optimize it for production.
// private container class incapsulated and working in the thread
class NetworkThreadContainer : public QObject
{
Q_OBJECT
public:
NetworkThreadContainer(QObject* parent=0):QObject(parent),client(0)
{
if (server.listen(QHostAddress::Any, 5200))
{
connect(&server, SIGNAL(newConnection()), this, acceptConnection());
}
else
{
// don't you want to throw exception here?
}
}
public slots:
void sendDataToClient(const QByteArray& barr)
{
if (client)
{
client->write(msg);
client->waitForBytesWritten();
}
}
void acceptConnection()
{
if (!client)
{
client = server.nextPendingConnection();
connect(client, SIGNAL(readyRead()), this, SLOT(receive()));
}
else
{
// what will you do with more than one client connections or reconnections?
}
}
void NetworkSupport::receive()
{
if (client)
{
QByteArray curMessage = client->readAll();
emit messageReceived(curMessage);
}
}
signals:
void messageReceived(const QByteArray&);
public:
QTcpClient* client;
QTcpServer server;
};
// thread class visible to outer program
class NetworkThread : public QThread
{
Q_OBJECT
public:
NetworkThread(QObject* parent=0):QObject(parent)
{
start();
}
~NetworkThread()
{
quit();
wait();
}
bool sendDataToClient(QByteArray* barr)
{
bool ok=false;
// async send data to container's thread
mutex.lock();
if (container)
{
ok=true;
QMetaObject::invokeMethod(
container,
"sendDataToClient",
Qt::QueuedConnection,
Q_ARG(QByteArray, *barr)
);
}
else
{
// container either is not ready yet or already destroyed
}
mutex.unlock();
return ok;
}
signals:
void messageReceived(const QByteArray&);
protected:
void run()
{
mutex.lock();
// I would suggest to catch exception here and assign the error result to some variable for further processing
container=new NetworkThreadContainer();
mutex.unlock();
connect(container,SIGNAL(messageReceived(QByteArray),this,SIGNAL(messageReceived(QByteArray)),Qt::QueuedConnection);
exec();
mutex.lock();
delete container;
mutex.unlock();
}
private:
QMutex mutex;
QPointer<NetworkThreadContainer> container;
};

QThread with slots and signals does not seem to create a new thread

I'm having some issues with Qt threading to allow the threaded part to update the GUI of my program. Seems like it's a known "problem" with Qt, so I found multiple tutorials, but I don't understand why my example here is not working.
I inherited from QThread as follow:
class CaptureThread: public QThread {
Q_OBJECT
public:
CaptureThread(const QObject *handler, QPushButton *start) {
CaptureThread::connect(start, SIGNAL(clicked()), this, SLOT(capture_loop()));
CaptureThread::connect(this, SIGNAL(sendPacket(Packet*)),
this, SLOT(receivePacket(Packet*)));
}
signals:
void sendPacket(Packet*);
public slots:
void capture_loop() {
Packet *packet;
while (my_condition) {
packet = Somewhere::getPacket();
//getPacket is define somewhere and is working fine
emit(sendPacket(packet));
std::cout << "Sending packet!" << std::endl;
}
}
};
And here is the CaptureHandler:
class CaptureHandler: public QWidget {
Q_OBJECT
public:
CaptureHandler() {
start = new QPushButton("Capture", this);
thread = new CaptureThread(this, start);
thread->start();
}
public slots:
void receivePacket(Packet *packet) {
std::cout << "Packet received!" << std::endl;
/*
Here playing with layout etc...
*/
}
private:
QPushButton *start;
CaptureThread *thread;
};
I think the signals and slots are ok, because it displays on the terminal
Sending packet!
Packet received!
Sending packet!
Packet received!
Sending packet!
Packet received!
But in the receivePacket slot, i'm trying to modify my GUI, and it does not work. The GUI just freeze, and all I can do is CTRL+C on terminal.
So i think my capture_loop, which is an infinite loop for the moment, is blocking the program, which means my thread has not started.
But I called thread->start().
I even tried to start the thread in CaptureThread constructor, by calling this->start(), but the result is the same.
Any idea?
Your using QThread wrong. By just creating the thread, it will not execute on the thread. you will need to do it inside the QThread::run function (by overriding it), since thats the only one that will run on the new thread. Please notice, that as soon as you return from this function, the thread will exit.
If you want to use your own loop inside the QThread::run function (instead using Qts default event loop), the thread won't be able to receive signals inside the run-function!
Here an example on how to use QThread:
class CaptureThread: public QThread {
Q_OBJECT
public:
CaptureThread(const QObject *handler, QPushButton *start) {
//calling "start" will automatically run the `run` function on the new thread
CaptureThread::connect(start, SIGNAL(clicked()), this, SLOT(start()));
//use queued connection, this way the slot will be executed on the handlers thread
CaptureThread::connect(this, SIGNAL(sendPacket(Packet*)),
handler, SLOT(receivePacket(Packet*)), Qt::QueuedConnection);
}
signals:
void sendPacket(Packet*);
protected:
void run() {
Packet *packet;
while (my_condition) {
packet = Somewhere::getPacket();
//getPacket is define somewhere and is working fine
emit sendPacket(packet) ;//emit is not a function
qDebug() << "Sending packet!";//you can use qDebug
}
}
};

Qt Thread with ping operation in Linux

I am trying to accomplish what looks like a very simple task... but it's being a nightmare instead.
I developed an app for a Linux Laptop. Inside the app, I would like to have an independent thread that continuously pings another PC (eg once every 5 seconds, forever... well, as long as the laptop is switched on).
Of course, when the PC that the app is pinging is not connected, the app has to work smoothly, without waiting for the ping operation to return... How can I achieve this?
At first I used a QTimer with QProcess:execute("ping"...), which works fine. The problem is that if the other PC doesn't reply, my whole app and its GUI freeze for about one second at each ping operation. Changing the "ping" options (setting "-i0.2", for example), in order to reduce waiting time for the ping operation, didn't help: when the other PC is not connected my app becomes very slow. If I remove the ping, of course everything works smoothly.
So, I decided to insert the "ping" operation in a QThread, but when I try to follow the second example in http://doc.qt.io/qt-4.8/qthread.html, nothing seems to work: the app doesn't even start.
Here's the code:
//Pinger.h
class Pinger : public QThread
{
Q_OBJECT
void run();
public:
void setParam(const QString &urlToPing); // it sets the url to ping
signals:
/// \brief Signal emitted when pinging of specified url fails
void pingFailed(int ok);
private:
QString pingurl;
};
//Pinger.cpp
void Pinger::run()
{
int exitCode;
QProcess pingProc;
while(true)
{
exitCode=pingProc.execute("ping",QStringList() << "-c 1" << "-i0.2" << pingurl);
emit pingFailed(exitCode);
sleep(5);
}
}
// MainWindow::MainWindow
pinga= new Pinger(); // defined in mainwindow.h as Pinger* Pinga
pinga->setParam(ip_address);
connect(pinga,SIGNAL(pingFailed(int)),this,SLOT(connectionLost(int)));
connect(pinga,SIGNAL(finished()),pinga,SLOT(deleteLater()));
pinga->start();
Has anyone tried anything similar? I am quite new to Qt, but this operation seems so trivial that I find it incredible that there's no clear way to implement it. so I hope I am just missing something obvious.
If QThread is used it is better to avoid sleep(5); and the loop while(true), since the thread cannot be closed gracefully without killing it. Instead of the loop and blocking sleep it is better to call that periodic task again by single shot timer initiated when the previous task (process execution) is finished. However, in that case that logic should be implemented in some other member slot (Pinger::doWork()). The slot run() should be kept with its default implementation that executes the tread event loop. The work can be started by connecting the QThread::started() signal with Pinger::doWork():
connect(pinga, SIGNAL(started()), pinga, SLOT(doWork()));
It is needed to be careful with QThread deletion. In general it is not good to delete QThread object by itself (calling deleteLater() from its finished() signal). It is better to stop the thread and to delete it in MainWindow destructor:
MainWindow::~MainWindow
{
// stop the even loop
pinga->quit();
// wait for finishing current thread task; it can work only
// if the thread is not blocked by while(true) with sleep
pinga->wait();
// delete if it is not a smart pointer
delete pinga;
}
It is also possible to use QProcess with its non-blocking API in the main GUI thread without QThread. In that case it should be started by QProcess::start() and the slots connected to the signals QProcess::error() and QProcess::finished() should be used to start the next iteration. Those slots also should not block the main thread, so the next ping should be started using QTimer once previous ping is finished.
Here is an example of the "Qt Way" to write your Pinger class. Note that no threads are needed. QProcess is used asynchronously, and reports its status through a Qt signal. Once you really grok Qt, you'll realize that using threads is very rarely the right or most natural solution to these types of problems.
Note that I'm using Qt 5 with C++11 support enabled to connect Qt signals to C++11 lambdas... you could just as easily write it in Qt4 style but it wouldn't be as compact and readable.
class Pinger : public QObject
{
Q_OBJECT
public:
Pinger(QObject *parent = 0) : QObject(parent)
{
//Have to do this ugliness because QProcess::finished is overloaded
auto finishedFunc = static_cast<void(QProcess::*)(int)>(&QProcess::finished);
connect(&m_process, finishedFunc, [this](int exitCode)
{
if( exitCode == 0 )
{
emit pingSuccess();
}
else
{
emit pingFailed(exitCode);
}
});
}
void run(const QString& hostToPing, int intervalInSeconds)
{
m_host = hostToPing;
QTimer* timer = new QTimer(this);
timer->start(intervalInSeconds * 1000);
QObject::connect(timer, &QTimer::timeout, [this]()
{
if ( m_process.state() == QProcess::NotRunning )
{
m_process.start(QString("ping -c 1 -W 1 %1").arg(m_host));
}
else
{
qDebug() << "Cannot ping, previous ping operation still in progress!";
}
});
}
signals:
void pingSuccess();
void pingFailed(int exitCode);
private:
QProcess m_process;
QString m_host;
};
Using it is as simple as:
Pinger pinger;
QObject::connect(&pinger, &Pinger::pingSuccess, []()
{
qDebug() << "Host is up!";
});
QObject::connect(&pinger, &Pinger::pingFailed, [](int exitCode)
{
qDebug() << "Host is unreachable! Ping exit code = " << exitCode;
});
pinger.run("google.com", 3);
I had the same problem and fixed it by running the ping command in an own thread. By using a signal, I'm interacting with the non-blocking GUI.
1) I've declared a Ping class, which run the Ping command:
class Ping {
public:
static bool start(QString host) {
QStringList parameters;
#if defined(WIN32)
parameters << "-n" << "1";
#else
parameters << "-c 1";
#endif
parameters << host;
int exitCode = QProcess::execute("ping", parameters);
if (exitCode==0) {
return true;
} else {
return false;
}
}
};
2.) Additionally I've a NetworkReceiver class, which creates the Network Thread via a Slot (startConnectionCheck) and interacts with the GUI via a Signal (newConnectionStatus):
class NetworkReceiver : public QObject
{
Q_OBJECT
public:
explicit NetworkReceiver(QObject * parent = nullptr);
~NetworkReceiver();
void start() {
while(true) {
emit newConnectionStatus(Ping::start("google.at"));
QThread::sleep(5);
}
}
signals:
void newConnectionStatus(bool connected);
public slots:
void startConnectionCheck() {
QFuture<void> test = QtConcurrent::run(this, &NetworkReceiver::start);
}
};

QMutex in slots

Suppose to have the following QT code (QT 5.3.1):
void SenderClass::runSignal()
{
emit mySignal();
}
void ReceiverClass::ReceiverClass()
{
...
connect (senderClassRef, SIGNAL(mySignal()), this, SLOT(mySlot()) );
}
void ReceiverClass::mySlot()
{
//Long operation executions
Sleep(1000);
qDebug() << "1";
Sleep(1000);
qDebug() << "2";
Sleep(1000);
qDebug() << "3";
}
Calling runSignal() consecutively it happens that console displays something like:
1 2 1 3 2 3
The 2 classes live in the same thread.
Do I have to use a QMutexLocker in slots?? Or is there an another way to have an ordered output like:
1 2 3 1 2 3
so preventing a call of mySlot() function if there's still one executing.
UPDATE
Here follow a real code snippet.
The sender:
//Sender.h
#include <QtWidgets/QWidget>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTextBrowser>
class Receiver;
class Sender : public QWidget
{
Q_OBJECT
public:
Sender(QWidget *parent = 0);
QPushButton *pushButton;
QTextBrowser *textBrowser;
Receiver* receiver;
signals:
void buttonClickedSignal();
public slots:
void ButtonClickedSlot();
};
//Sender.cpp
#include "Sender.h"
#include "Receiver.h"
#include <QObject>
Sender::Sender(QWidget *parent)
: QWidget(parent)
{
pushButton = new QPushButton(this);
pushButton->setObjectName(QStringLiteral("pushButton"));
pushButton->setGeometry(QRect(150, 30, 75, 23));
pushButton->setText("Button");
textBrowser = new QTextBrowser(this);
textBrowser->setObjectName(QStringLiteral("textBrowser"));
textBrowser->setGeometry(QRect(50, 90, 256, 192));
receiver = new Receiver(this);
QObject::connect(pushButton, SIGNAL(clicked()), this, SLOT(ButtonClickedSlot()));
}
void Sender::ButtonClickedSlot()
{
emit buttonClickedSignal();
}
The receiver:
//Receiver.h
#include "Sender.h"
class Receiver : QObject
{
Q_OBJECT
public:
Receiver(Sender *sender);
Sender *m_sender;
public slots:
void ReceiverSlot();
};
//Receiver.cpp
#include "Receiver.h"
#include <QObject>
#include <QThread>
#include <QApplication>
Receiver::Receiver(Sender *sender)
{
m_sender = sender;
QObject::connect(m_sender, SIGNAL(buttonClickedSignal()), this, SLOT(ReceiverSlot()), Qt::QueuedConnection);
}
void Receiver::ReceiverSlot()
{
m_sender->textBrowser->append("1");
QThread::msleep(100);
qApp->processEvents();
m_sender->textBrowser->append("2");
QThread::msleep(100);
qApp->processEvents();
m_sender->textBrowser->append("3");
QThread::msleep(100);
qApp->processEvents();
m_sender->textBrowser->append("4");
QThread::msleep(100);
qApp->processEvents();
}
Clicking the button quickly results in not consecutive numbers in QTextBrowser, even if a QueuedConnection is set.
UPDATE 2
What I would like to achieve is a queued access to the ReceiverSlot. The user can click the button with any freedom (when he wants and at any speed). The ReceiverSlot cannot miss any event. I need some sort of event queuing so that (long) operations in the ReceiverSlot are always executed, maybe delayed, but executed.
Get rid of QApplication::processEvents and move the long operation that freezes your GUI to a new thread. You can use for example a worker object that you move to a new thread. There is a good example of this in the docs.
You should also read this to learn more about threads and events in Qt. There is an exact same situation as yours explained in the forcing event dispatching part:
Be very careful when reentering the event loop “by other paths”: it can lead to unwanted recursions! Let’s go back to the Button example. If we call QCoreApplication::processEvents() inside the doWork() slot, and the user clicks again on the button, the doWork() slot will be invoked again:
main(int, char **) QApplication::exec()
[…]
QWidget::event(QEvent *)
Button::mousePressEvent(QMouseEvent *)
Button::clicked()
[…]
Worker::doWork() // first, inner invocation
QCoreApplication::processEvents() // we manually dispatch events and…
[…]
QWidget::event(QEvent * ) // another mouse click is sent to the
Button…
Button::mousePressEvent(QMouseEvent *)
Button::clicked() // which emits clicked() again…
[…]
Worker::doWork() // DANG! we’ve recursed into our slot.
In general you want to avoid QApplication::processEvents and creating local event loops. If you have these in your code, then you should redesign it so you won't have to use these.
Worker object examlpe from the docs:
class Worker : public QObject
{
Q_OBJECT
QThread workerThread;
public slots:
void doWork(const QString &parameter) {
// ...
emit resultReady(result);
}
signals:
void resultReady(const QString &result);
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(this, SIGNAL(operate(QString)), worker, SLOT(doWork(QString)));
connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
signals:
void operate(const QString &);
};
To turn my comments into a proper answer:
If, when in your slot you call qApp->processEvents() there is a mouse press event waiting to be delivered to the button, it will be delivered, and the clicked() signal will be emitted again, and thus your slot will be called again.
A QMutex will not help, because there is no threading involved. Adding one would most likely result in your program deadlocking.
Using Qt::QueuedConnection doesn't help, because this posts an event to the queue which, when processed, will result in your slot being invoked. So it just, at best, delays it until the next time you call qApp->processEvents().
In the context of your example, my suggestion would be to get rid of the queued connection, and instead disable the button while the slot is working. This will prevent the clicked() signal being emitted again, and also show the user that they shouldn't be trying to click on the button at this time.
If for whatever reason you don't want to do that, you could add a flag to your receiver class to prevent recursive calls. Something like this will do the trick, although it's not very pretty:
void Receiver::ReceiverSlot()
{
if( m_inSlot )
return;
m_inSlot = true;
// do work
m_inSlot = false;
}