Change signal-slot connection in a slot when called - qt

I have a use case when user actions initialise application data at each runtime. To represent that behavior here is my sample code:
SignalSlotChange.h
#ifndef SIGNALSLOTCHANGE_H
#define SIGNALSLOTCHANGE_H
#include <QtGui>
class SignalSlotChange : public QWidget
{
Q_OBJECT
public:
SignalSlotChange(QWidget *parent = 0);
private slots:
void firstCall();
void secondCall();
private:
QPushButton *button;
};
#endif // SIGNALSLOTCHANGE_H
SignalSlotChange.cpp
#include "SignalSlotChange.h"
SignalSlotChange::SignalSlotChange(QWidget *parent) : QWidget(parent)
{
button = new QPushButton("Messgage", this);
QObject::connect(button, SIGNAL(clicked()), this, SLOT(firstCall()));
show();
}
void SignalSlotChange::firstCall()
{
QMessageBox::information(this, "SignalSlotChange", "First call", QMessageBox::Ok, QMessageBox::NoButton);
// Change the signal-slot connection to secondCall()
QObject::disconnect(button, SIGNAL(clicked()), this, SLOT(firstCall()));
QObject::connect(button, SIGNAL(clicked()), this, SLOT(secondCall()));
}
void SignalSlotChange::secondCall()
{
QMessageBox::information(this, "SignalSlotChange", "Second call", QMessageBox::Ok, QMessageBox::NoButton);
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
SignalSlotChange ssc;
return app.exec();
}
When the button is pressed then the initialising slot firstCall() is called. It changed the signal-slot connection to secondCall() for subsequent signals.
The problem with the way I do it is that it is highly coupled and requires that slot knows exact method and signals to change it.
With QObject::sender() I would know origin of sender but not its signal, and I would know about just one sender which happened to emit that signal.
I can do it with a Boolean first_call but that would be making a check on Boolean value for all subsequent calls, something which I want to avoid and hence this question.

A somewhat different solution could be implemented by using a pointer-to-member approach:
SignalSlotChange.h
#ifndef SIGNALSLOTCHANGE_H
#define SIGNALSLOTCHANGE_H
#include <QtGui>
class SignalSlotChange : public QWidget {
Q_OBJECT
public:
SignalSlotChange(QWidget *parent = 0);
private slots:
void callCall();
private:
void (SignalSlotChange::* delegate) ();
void firstCall();
void secondCall();
QPushButton *button;
};
#endif // SIGNALSLOTCHANGE_H
SignalSlotChange.cpp
#include "SignalSlotChange.h"
SignalSlotChange::SignalSlotChange(QWidget *parent) : QWidget(parent) {
delegate = &SignalSlotChange::firstCall;
button = new QPushButton("Messgage", this);
QObject::connect(button, SIGNAL(clicked()), this, SLOT(callCall()));
show();
}
void SignalSlotChange::callCall() {
(this->*delegate) ();
}
void SignalSlotChange::firstCall() {
QMessageBox::information(this, "SignalSlotChange", "First call", QMessageBox::Ok, QMessageBox::NoButton);
// Change the effective signal-slot connection to secondCall()
delegate = &SignalSlotChange::secondCall;
}
void SignalSlotChange::secondCall() {
QMessageBox::information(this, "SignalSlotChange", "Second call", QMessageBox::Ok, QMessageBox::NoButton);
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
SignalSlotChange ssc;
return app.exec();
}
I am not sure if this is better solution from a decoupling perspective, but the advantage is that the mechanism does not need to know anything about the slot which was actually triggered. The complete logic to implement the "method switch" is encapsulated within the SignalSlotChange class. The callCall() slot can be connected to any other compatible signal and the method switch still works, with no further code changes.

Related

Why is the Qt event loop left

I want a small program doing the following:
start a single-shot QTimer
when it times out, a QMessageBox is shown
if button "Continue" is clicked, the box is closed and the timer restarted
if button "Stop" is clicked, the box is closed and the application exited
The problem I have is that the event loop is left as soon as I hide the message box. The box is displayed only once. I reproduced my program in a console version and it runs as expected. Here is my code. Thanks in advance for your help.
main.c
#include <QtGui/QApplication>
#include "TimedDialog.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
TimedDialog dialog(&app, SLOT(quit()));
dialog.run();
return app.exec();
}
TimedDialog.h
#ifndef TIMED_DIALOG_H
#define TIMED_DIALOG_H
#include <QObject>
#include <QTimer>
class QMessageBox;
class QPushButton;
class QAbstractButton;
class TimedDialog : public QObject
{
Q_OBJECT
public:
TimedDialog(
QObject const * receiver,
char const * method);
~TimedDialog();
void run(void);
signals:
void stop(void);
private:
QMessageBox* _box;
QPushButton* _buttonContinue;
QPushButton* _buttonStop;
QTimer _timer;
static int const DELAY = 2 * 1000; // [ms]
private slots:
void onTimeout(void);
void onButtonClicked(QAbstractButton * button);
};
#endif
TimedDialog.cpp
#include <assert.h>
#include <QMessageBox>
#include <QPushButton>
#include "TimedDialog.h"
TimedDialog::TimedDialog(
QObject const * receiver,
char const * method)
: QObject(),
_box(0),
_buttonContinue(0),
_buttonStop(0),
_timer()
{
_box = new QMessageBox();
_box->setWindowModality(Qt::NonModal);
_box->setText("Here is my message!");
_buttonContinue = new QPushButton("Continue");
_box->addButton(_buttonContinue, QMessageBox::AcceptRole);
_buttonStop = new QPushButton("Stop");
_box->addButton(_buttonStop, QMessageBox::RejectRole);
_timer.setSingleShot(true);
assert(connect(&_timer, SIGNAL(timeout()), this, SLOT(onTimeout())));
assert(connect(_box, SIGNAL(buttonClicked(QAbstractButton *)), this, SLOT(onButtonClicked(QAbstractButton *))));
assert(connect(this, SIGNAL(stop()), receiver, method));
}
TimedDialog::~TimedDialog()
{
delete _box;
}
void TimedDialog::onTimeout(void)
{
_box->show();
}
void TimedDialog::onButtonClicked(QAbstractButton * button)
{
_box->hide();
if (button == _buttonContinue)
{
_timer.start(DELAY);
}
else
{
emit stop();
}
}
void TimedDialog::run(void)
{
_timer.start(DELAY);
}
The created timer is being set as singleshot, which has already called timeout.
If you look at QTimer source code for the call to start() you can see it states: -
If singleShot is true, the timer will be activated only once.
You could fix and simplify the code with the class function QTimer::singleShot

Q_EMIT not working from connected slot, only with QTimer slot

I am trying to emit a signal, which works if the slot if hooked up to a QTimer. However if the slot is hooked up to a QPushButton for example, it doesn't work. For example, I have a run() function that it connected to a 1 second QTimer. The run() function contains Q_EMIT textChanged("Test") This signal works as expected. However, if I have a QPushButton connected to a slot, which also contains Q_EMIT textChanged("Test") nothing happens...why is this???
#include <QApplication>
#include <QVBoxLayout>
#include <QPlainTextEdit>
#include <QTabWidget>
#include <QTimer>
#include <QPushButton>
class Counter : public QWidget
{
Q_OBJECT
public:
explicit Counter(QWidget *parent = 0) : QWidget(parent) {
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), SLOT(run()));
timer->start(1000);
QVBoxLayout *layout = new QVBoxLayout(this);
QPushButton *OK = new QPushButton("OK");
connect(OK, SIGNAL(clicked()), SLOT(OKalarmLimits()));
layout->addWidget(OK);
}
Q_SIGNAL void textChanged(const QString &text);
Q_SLOT void run() { Q_EMIT textChanged("Run - Counter"); }
Q_SLOT void OKalarmLimits() { Q_EMIT textChanged("Button Clicked"); }
};
class MainWindow : public QWidget {
Q_OBJECT
QPlainTextEdit *box;
public:
explicit MainWindow(QWidget *parent = 0) : QWidget(parent) {
QVBoxLayout * layout = new QVBoxLayout(this);
box = new QPlainTextEdit();
box->setMaximumHeight(400);
box->setMinimumWidth(400);
layout->addWidget(box);
QTabWidget *tabWidget = new QTabWidget;
tabWidget->addTab(new Counter(), tr("Counter"));
layout->addWidget(tabWidget);
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), SLOT(run()));
timer->start(1000);
}
Q_SLOT void updateWidgets(const QString &t) { box->appendPlainText(t); }
Q_SLOT void run() { box->appendPlainText("Run - Window"); }
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow s;
Counter m;
s.show();
s.connect(&m, SIGNAL(textChanged(QString)), SLOT(updateWidgets(QString)));
return a.exec();
}
#include "main.moc"
It's real easy. There are two different counters, and you're connecting to the wrong one.
The Counter instance in main(), the one that you connect to, is never shown (you don't call its show() method after all!). You need to connect to the instance that is created in this line: tabWidget->addTab(new Counter(), tr("Counter"));
One solution would be do the connection in MainWindow():
Counter * counter = new Counter();
QObject::connect(counter, SIGNAL(textChanged(QString)), SLOT(updateWidgets(QString)));
QTabWidget *tabWidget = new QTabWidget;
tabWidget->addTab(counter, tr("Counter"));
layout->addWidget(tabWidget,1,0);
This also illustrates why minimal examples should really be minimal. Were you to continue working on minimization, you'd have found the bug. Essentially, you can delete the lines below from main() without any change in behavior: this would be a dead giveaway that the counter you think of is not the one.
Counter m;
QObject::connect(&m, SIGNAL(textChanged(QString)), &s,SLOT(updateWidgets(QString)));

'QMessageBox::critical' : none of the 4 overloads could convert all the argument types

I want to display an error message whenever my independent thread encounters the word "alert1" in a specific .txt file. But I get the above error inside the monitorForAlerts() inside mythread.cpp file. The line expectedly executes if I were to place it inside dialog.cpp. So I guess this is due to non-inheritance of this object. Can you please advise me how to solve this error for the given code?
Here is the code:
dialog.h
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QtCore>
#include "mythread.h"
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
public slots:
private:
Ui::Dialog *ui;
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
};
#endif // DIALOG_H
mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <QtCore>
#include <QDebug>
#include <QFile>
#include <Windows.h>
#include <QMessageBox>
#include <QTimer>
#define ALERTS_MESSAGE_STORAGE_PATH "E:\\QT1\\simpleGUIThread2\\simpleGUIThread2\\usbAlert.txt"
#define TIMER_VALUE 500
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
void run();
QString name;
void monitorForAlerts();
int exec();
public slots:
signals:
void testSignal(QString message);
public slots:
};
#endif // MYTHREAD_H
dialog.cpp
#include "dialog.h"
#include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
}
Dialog::~Dialog()
{
delete ui;
}
void Dialog::on_pushButton_clicked()
{
ui->label->show();
}
void Dialog::on_pushButton_2_clicked()
{
ui->label->hide();
}
mythread.cpp
#include "mythread.h"
#include "dialog.h"
MyThread::MyThread(QObject *parent) :
QThread(parent)
{
}
void MyThread::run()
{
exec();
}
int MyThread::exec()
{
while(1)
{
monitorForAlerts();
emit(testSignal("hello world!!"));
sleep(1);
}
}
void MyThread::monitorForAlerts()
{
QString response = ALERTS_MESSAGE_STORAGE_PATH;
QFile resp(response);
resp.open(QIODevice::WriteOnly);
resp.close();
QFile resp1(response);
char buf[121];
char buf1[] = "alert1";
char buf2[] = "alert2";
resp1.open(QIODevice::ReadOnly);
while(resp1.size() == 0)
{
Sleep(3000);
}
qint64 lineLength = resp1.readLine(buf, sizeof(buf));
resp1.close();
if(strcmp(buf,buf1) == 0)
{
QFile::remove(ALERTS_MESSAGE_STORAGE_PATH);
qDebug()<<"warning 1!!";
QMessageBox::critical(this,tr("ERROR"),tr("Large change in illumination.\nPlease re-capture reference image.\n"));
}
if(strcmp(buf,buf2) == 0)
{
QFile::remove(ALERTS_MESSAGE_STORAGE_PATH);
qDebug()<<"warning 2!!";
QMessageBox::critical(this,tr("ERROR"),tr("The camera position has been moved or an object is obscuring its view.\nPlease check the device.\n"));
}
}
main.cpp
#include "dialog.h"
#include <QApplication>
#include "mythread.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyThread mThread1;
mThread1.name = "mThread1";
mThread1.start();
Dialog w;
w.show();
return a.exec();
}
LATEST UPDATE*********************************************************************
Hi Zlatomir,
I choose to take your 1st advice. I have created a signal that the thread will emit and connect it to a slot for QDialog. Please let me know if my understanding is correct, because I do not know where to implement the connect(), since the signal is declared in mythread.h and the slot in dialog.h. The connection type argument for connect is Qt::QueuedConnection, so that gui elements from another thread different than main-thread.
are NOT created. Is this statement correct? and where do I place this?
connect( mThread, SIGNAL(alertSignal(QString)), this, SLOT(alertSlot(QString)), Qt::QueuedConnection);
mythread.h
//....
signals:
void alertSignal(QString message);
//....
dialog.h
//....
public slots:
void alertSlot(QString message);
//....
mythread.cpp
//....
if(strcmp(buf,buf1) == 0)
{
QFile::remove(ALERTS_MESSAGE_STORAGE_PATH);
qDebug()<<"warning 1!!";
emit(alertSignal("alert1"));
}
else if(strcmp(buf,buf2) == 0)
{
QFile::remove(ALERTS_MESSAGE_STORAGE_PATH);
qDebug()<<"warning 2!!";
emit(alertSignal("alert2"));
}
dialog.cpp
void Dialog::alertSlot(QString message)
{
if(strcmp(message, "alert1"))
QMessageBox::critical(this,tr("ERROR"),tr("Large change in illumination.\nPlease re-capture reference image.\n"));
else if(strcmp(message, "alert2"))
QMessageBox::critical(this,tr("ERROR"),tr("The camera position has been moved or an object is obscuring its view.\nPlease check the device.\n"));
}
Now if this were correct, how do i implement the connect() and in which file?
The first argument is the problem, in your case this is not a good argument, because there this is a pointer to a MyThread instance, and MyThread is not a QWidget (is not derived from QWidget).
To solve this you can show the QMessageBox::critical from a slot in mainwindow (the Dialog class in your code, there you pass the instance of main-window that is a QWidget) and connect that slot with a signal that you emit from your thread, make sure that the connection type argument for connect is Qt::QueuedConnection, so that you don't try to create gui elements from another thread different than main-thread.
Another option would be to validate the data before you start the second thread and to tell
the user that he needs to provide the right files.
LE: Also check the QThread's documentation for the recommended way to use the class, now it's recommended not to derive from QThread.
LE2 - answer to the update
That connect can be made where ever you can have the two instances that you want to connect, in your case main.cpp is a good place to connect those (don't forget to fully qualify the name for connect: QObject::connect):
//...
MyThread mThread1;
mThread1.name = "mThread1";
mThread1.start();
Dialog w;
QObject::connect( &mThread1, SIGNAL(alertSignal(QString)), &w, SLOT(alertSlot(QString)), Qt::QueuedConnection);
w.show();
//...

Connecting signals and slots in a QTextEdit subclass?

I derived a class from QTextEdit and use it as a "logbook". I equipped it with a slot to receive log-messages.
class CLogbook : public QTextEdit
{
Q_OBJECT;
public:
void log(QString msg) {append(msg)};
public slots:
void recvLogSignal(const QString message)
{
append("hallo");
std::cout << "signal received.\n";
log(message);
}
};
another class then emits a signal like this:
// in the header
signals:
void logMessage(const QString);
// in the implementation
emit logMessage("qt is cute");
std::cout << "if you can read this the logMessage was emitted\n";
and also i connect the signal to the slot
connect(tableeditor, SIGNAL(logMessage(const QString)), logbook, SLOT(recvLogSignal(const QString)));
However the message is never shown in the "logbook". What am i missing here?
SOLVED: The connect method was called after emitting the signal :-(
It is hard to see exactly what is wrong with your implementation without a full example. Sometimes signals or slots will fail if an object goes out of scope if it isn't initialized on the heap.
Another way that it could fail is if your QApplication hasn't reached the exec() call.
I haven't experimented with using const in signal and slot calls, and I haven't seen it in any examples before, so that could be causing the problem; but it seems to work fine in the example below.
Working Example With a Derived Class of QTextEdit
Here is a simple example I put together that does some basic logging with a push button and a line edit.
Here is the header:
#ifndef WIDGET_H
#define WIDGET_H
#include <QtGui/QWidget>
//#include <QTextEdit>
#include "clogbook.h"
#include <QLineEdit>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget() {}
public slots:
void on_pushButton();
void on_lineEditReturn();
private:
CLogBook * text_edit_log;
QLineEdit * line_edit;
};
#endif // WIDGET_H
Here is the source:
#include "widget.h"
#include <QBoxLayout>
#include <QPushButton>
#include <QTimer>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QBoxLayout * box_layout = new QBoxLayout(QBoxLayout::TopToBottom);
text_edit_log = new CLogBook;
line_edit = new QLineEdit("Type Here and press Enter.");
QPushButton * push_button = new QPushButton("Click to Add To Log");
text_edit_log->setText("My Log Book");
box_layout->addWidget(text_edit_log);
box_layout->addWidget(line_edit);
box_layout->addWidget(push_button);
this->setLayout(box_layout);
QObject::connect(push_button, SIGNAL(clicked()), this, SLOT(on_pushButton()));
QObject::connect(line_edit, SIGNAL(returnPressed()), this, SLOT(on_lineEditReturn()));
}
void Widget::on_pushButton()
{
// text_edit_log->append("Push Button Logging Test");
text_edit_log->recvLogSignal("Push button test.");
}
void Widget::on_lineEditReturn()
{
// text_edit_log->append(line_edit->text());
text_edit_log->recvLogSignal(QString("LineEdit: ") + line_edit->text() );
line_edit->clear();
}
And here is the main:
#include <QtGui/QApplication>
#include "widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
And here is the CLogBook class:
#ifndef CLOGBOOK_H
#define CLOGBOOK_H
#include <QTextEdit>
#include <iostream>
class CLogBook : public QTextEdit
{
Q_OBJECT
public:
explicit CLogBook(QWidget *parent = 0) : QTextEdit(parent) { }
void log (QString msg) { append(msg); }
public slots:
void recvLogSignal(const QString message)
{
append("hallo");
std::cout << "signal received.\n" << std::endl;
log(message);
}
};
#endif // CLOGBOOK_H

How to repaint another Qt class

I'm a new bit in Qt...
I have a Qt GUI application (written by me), let's call it QtAPP.exe
When QtAPP.exe running, I will use a QThread and QProcess to execute some external file,
such as player.exe (written in native C).
Here's my question:
In QtAPP.exe, there are 2 classes,
1. QMainWindow - Core of QtAPP.exe
2. QThread - A thread class to execute external things
For now, if I got a finished() signal in that QThread,
how do I to force the QMainWindow to repaint itself ?
Hope somebody can show me some tips, maybe sample code :)
Any suggestion are welcome~
One solution would be to simply connect the finished() signal to a slot in MainWindow whose implementation calls update(). Note that delivery of this signal will be asynchronous because the sender and receiver objects are in different threads.
Here is a working example:
main.cpp
#include <QtGui/QApplication>
#include "stuff.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow w;
w.show();
return app.exec();
}
stuff.h
#ifndef STUFF_H
#define STUFF_H
#include <QtGui/QMainWindow>
#include <QtCore/QThread>
class QLabel;
class Thread : public QThread
{
Q_OBJECT
public:
Thread(QObject *parent);
void run();
private:
void startWork();
signals:
void workFinished();
};
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow();
public slots:
void startWork();
void workFinished();
private:
QLabel* m_label;
Thread* m_thread;
};
#endif
stuff.cpp
#include <QtCore/QTimer>
#include <QtCore/QMutex>
#include <QtCore/QWaitCondition>
#include <QtGui/QVBoxLayout>
#include <QtGui/QPushButton>
#include <QtGui/QLabel>
#include "stuff.h"
#include <QDebug>
// Global variables used for ITC
QWaitCondition buttonPressed;
QMutex mutex;
Thread::Thread(QObject *parent)
: QThread(parent)
{
}
void Thread::run()
{
qDebug() << "Thread::run" << QThread::currentThreadId();
while (1) {
mutex.lock();
buttonPressed.wait(&mutex);
mutex.unlock();
startWork();
}
}
void Thread::startWork()
{
qDebug() << "Thread::startWork" << QThread::currentThreadId();
// Simulate some long-running task
sleep(3);
// Emit a signal, which will be received in the main thread
emit workFinished();
}
MainWindow::MainWindow()
: m_label(new QLabel(this))
, m_thread(new Thread(this))
{
QPushButton *button = new QPushButton("Start", this);
connect(button, SIGNAL(pressed()), this, SLOT(startWork()));
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(button);
layout->addWidget(m_label);
setLayout(layout);
// Create connection across thread boundary
connect(m_thread, SIGNAL(workFinished()), this, SLOT(workFinished()));
m_thread->start();
}
void MainWindow::startWork()
{
// Signal the thread to tell it that the button has been pressed
mutex.lock();
m_label->setText("Started");
buttonPressed.wakeAll();
mutex.unlock();
}
void MainWindow::workFinished()
{
qDebug() << "MainWindow::workFinished" << QThread::currentThreadId();
m_label->setText("Finished");
}

Resources