I have a QTimer object that kicks off a function every second. The timer runs correctly if I do the following
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
// Set a Qtimer to update the OSD display every 1 second
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(print()));
timer->start(1000);
}
void MainWindow::print()
{
printf("hello world\n");
}
But I want to pass a variable to print(). But when I do this, I never see my print statement.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
// Set a Qtimer to update the OSD display every 1 second
QTimer *timer = new QTimer(this);
int val = 42;
// Now pass val to print()
connect(timer, SIGNAL(timeout()), this, SLOT(print(val)));
timer->start(1000);
}
void MainWindow::print(int val)
{
// I never see my print statement
printf("hello world, val=%d\n", val);
}
header.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void print(int val);
Why does this not work? What can I do to pass a variable into print() using QTimer?
connect(timer, SIGNAL(timeout()), this, SLOT(print(val)));
Qt signal/slot connections don't work that way. The text within the SIGNAL() and SLOT() macros has to be the signature of the signal/slot method, verbatim; you can't put variables or other non-method-signature text there.
If you look at your program's stdout while it runs, you'll see an error message printed by connect() telling you that it can't find any slot-method named print(val).
If you want to provide a separate value for your slot, you could either make val a member-variable of your class, and have print() look at the member variable instead of an argument, or you could use an intermediary slot, like this:
public slots:
void print(int val);
void print42() {print(val);}
... and then connect your signal to SLOT(print42()) instead.
Related
// splashscreen.h
class SplashScreen : public QMainWindow
{
Q_OBJECT
public:
explicit SplashScreen(QWidget *parent = nullptr);
~SplashScreen();
QTimer *mtimer;
public slots:
void update();
private:
Ui_SplashScreen *ui;
};
// app.h
#include "splashscreen.h"
class App: public QMainWindow
{
Q_OBJECT
public:
App(QWidget *parent = nullptr);
~App();
SplashScreen s;
private:
Ui::AppClass ui;
};
// app.cpp
App::App(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
QGraphicsOpacityEffect* eff = new QGraphicsOpacityEffect();
s.centralWidget()->setGraphicsEffect(eff);
QPropertyAnimation* a = new QPropertyAnimation(eff, "opacity");
a->setDuration(2000);
a->setStartValue(0);
a->setEndValue(1);
a->start(QPropertyAnimation::DeleteWhenStopped);
s.show();
connect(a, &QAbstractAnimation::finished, this, [this]
{
auto *timer = new QTimer();
this->s.mtimer = timer;
QObject::connect(timer, SIGNAL(timeout()), this->s, SLOT(update()));
timer->start(100);
});
}
I'm getting an error at this line: QObject::connect(timer, SIGNAL(timeout()), this->s, SLOT(update()));
no instance of overloaded function "QObject::connect" matches the argument list
I think it's mismatching the class signal, as this passed to the lambda refers to App not SplashScreen.
When i try to pass s (SplashScreen) to the lambda:
connect(a, &QAbstractAnimation::finished, this, [s]
{ ... }
I get an error: App::s is not a variable.
I'm confused, what is the proper way to connect in this case?
In App class, s is an instance, not a pointer to an instance. Function connect needs pointer, not reference.
Use these syntax should help:
QObject::connect(timer, &QTimer::timeout, &s, &SplashScreen::update);
Use these syntax:
QObject::connect(timer, &QTimer::timeout, this, &SplashScreen::update);
Playing with QMetaObject::invokeMethod method :
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::setText( int value)
{
QString s = QString::number(value);
ui->textEdit->setText(s);
}
void MainWindow::on_pushButton_clicked()
{
QGenericArgument genericArg = Q_ARG(int, 321);
bool inv = QMetaObject::invokeMethod( this,"setText",Qt::BlockingQueuedConnection, genericArg);
qDebug("inv = %d\n", inv);
}
QMetaObject::invokeMethod returns false.
I'm not sure regarding slot "setText". I took it from function name and I suppose it might be related. Where I can find list of slots at all? Should I create special slot for "setText"?
Maybe it is related to fact I run it from the same thread?
UPD:
I have added public slot instead of public method:
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
//void setText( int value);
private slots:
void on_pushButton_clicked();
public slots:
void setText(int value);
private:
Ui::MainWindow *ui;
};
And this helped, but why I'm getting 0 in setText value ?
Your button-event is handled by the event loop that owns the MainWindow object and this same object also contains the method you wish to invoke (setText()). That means that caller and callee (or signal and slot) in your case live on the same thread, and you must not use Qt::BlockingQueuedConnection! To quote the manual: Using this connection type to communicate between objects in the same thread will lead to deadlocks.
If you intent to do processing in your on_pushButton_clicked() after the setText() method has finished, use a Qt::DirectConnection instead, then your setText() will be called as if it was a simple function, and control returns to your clicked() function after setText() finished.
If you intent to finish processing all code in your on_pushButton_clicked() before processing of the setText() function starts, use a Qt::QueuedConnection.
If you wish to execute setText() in parallel to on_pushButton_clicked(), then move your setText() method to another object (which is owned by another thread). Only in this scenario a Qt::BlockingQueuedConnection makes sense.
For the project I am working on in Qt I need to make several things happen at the same time. One of these events is to take a temperature reading and display that reading in a text edit box along with a time stamp. The temp and time stamp do not display until the while loop i wrote finishes. I know the while loop is blocking it so I am trying to write a thread to display the time and temp, but can not figure out how to write to the gui from the thread.
Here is where I start the thread and the while loop
QThread cThread;
timeTempObject cObject;
cObject.DoSetup(cThread);
cObject.moveToThread(&cThread);
cThread.start();
while(flowTime > 0)
{
// set zero pin to be high while flowtime is more than 0
digitalWrite(0,1);
displayCurrentTime();
// set second pin LED to flash according to dutyCycle
digitalWrite(2,1);
delay(onTime);
// displayCurrentTime();
ui->tempTimeNoHeatMode->append(temp);
digitalWrite(2,0);
delay(offTime);
flowTime--;
}
noheatmode.h
namespace Ui {
class noheatmode;
}
class noheatmode : public QWidget
{
Q_OBJECT
public:
explicit noheatmode(QWidget *parent = 0);
~noheatmode();
private slots:
void on_startButtonNoHeatMode_clicked();
void on_noHeatModeBack_clicked();
public slots:
void displayCurrentTime();
private:
Ui::noheatmode *ui;
};
#endif // NOHEATMODE_H
timetempobject.h for the thread
class timeTempObject : public QObject
{
Q_OBJECT
public:
explicit timeTempObject(QObject *parent = 0);
void DoSetup(QThread &cThread);
public slots:
void DoWork();
};
#endif // TIMETEMPOBJECT_H
timetempobject.cpp
timeTempObject::timeTempObject(QObject *parent) :
QObject(parent)
{
}
void timeTempObject::DoSetup(QThread &cThread)
{
connect(&cThread,SIGNAL(started()),this,SLOT(DoWork()));
}
void timeTempObject::DoWork()
{
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(displayCurrentTime()));
// delay set to space out time readings, can be adjusted
timer->start(1500);
// Gets the time
QTime time = QTime::currentTime();
// Converts to string with chosen format
QString sTime = time.toString("hh:mm:ss:ms");
// displays current time in text edit box
Ui::noheatmode* noheatmode::ui->tempTimeNoHeatMode->append(sTime);
}
How do I alter my thread so it can write to the text editor in my gui?
Since QTextEdit::append is a slot, it's very easy to call it from other threads:
void tempTimeObject::DoWork() {
...
QMetaObject::invokeMethod(ui->tempTimeNoHeatMode, "append",
Qt::QueuedConnection, Q_ARG(QString, temp));
...
}
If you wished to execute arbitrary code, it boils down to "how to execute a functor in a given thread", with the thread being the main thread. The answers to this question provide multiple ways of doing it.
The simplest way on Qt 5 would be:
void tempTimeObject::DoWork() {
...
{
QObject signalSource;
QObject::connect(&signalSource, &QObject::destroyed, qApp, [=](QObject *){
ui->tempTimeNoHeatMode->append(text);
... // other GUI manipulations
});
} // here signalSource emits the signal and posts the functor to the GUI thread
...
}
I have been reading about Qt signals and slots and I am trying to get this to work, but until now without success. I hope someone can point me in the right direction.
I have two files, homeCommand.cpp and messagelogcommand.cpp. I have a QPlainTextEdit object in messagelogcommand.cpp that I want to update from homeCommand.cpp.
How can I do this using signals and slots? My signal is being called, as my QDebug is being printed out once a second, however the widget does not update.
This is what I am trying to do:
In MessageLogCommand.h
class MessageLogCommand : public QWidget
{
Q_OBJECT
public:
explicit MessageLogCommand(QWidget *parent = 0);
QLabel *homeLabel;
QPlainTextEdit *messageLog;
public Q_SLOTS:
void updateWidgets(const QString &text);
};
homeCommand.h
class homeCommand : public QWidget
{
Q_OBJECT
Q_SIGNALS:
void textChanged(const QString &text);
public:
explicit homeCommand(QWidget *parent = 0);
public slots:
void run(void);
void getHealthStatusPacket(void);
homeCommand.cpp
homeCommand::homeCommand(QWidget *parent) : QWidget(parent)
{
...
//Timer
QTimer *timer = new QTimer(this);
timer->setSingleShot(false);
connect(timer, SIGNAL(timeout()), this, SLOT(run()));
timer->start(1000);
setLayout(layout);
}
void homeCommand::run(void)
{
getHealthStatusPacket();
}
void homeCommand::getHealthStatusPacket(void)
{
...
Q_EMIT textChanged("ZOMG");
}
In MessageLogCommand.cpp
MessageLogCommand::MessageLogCommand(QWidget *parent) : QWidget(parent)
{
QGridLayout *layout = new QGridLayout;
QWidget::setFixedHeight(600);
//Sub-system Label
homeLabel = new QLabel("GSS Message Log");
QFont subsystemFont = homeLabel->font();
subsystemFont.setPointSize(12);
subsystemFont.setBold(true);
homeLabel->setFont(subsystemFont);
layout->addWidget(homeLabel, 0, 0);
//Event Log
messageLog = new QPlainTextEdit();
messageLog->setFixedHeight(500);
messageLog->setFixedWidth(600);
layout->addWidget(messageLog, 2,0);
setLayout(layout);
}
void MessageLogCommand::updateWidgets(const QString &text)
{
qDebug() << "Here";
messageLog->appendPlainText(text);
}
In main.cpp
MessageLogCommand s;
homeCommand m;
QObject::connect(&m, SIGNAL(textChanged(QString)), &s, SLOT(updateWidgets(QString)));
A very basic example is:
class MainClass:public QObject //class must be derived from QObject!
{
Q_OBJECT //this macro must be in the class definition
//so the moc compiler can generate the necessary glue code
public:
void doSomething() {
...
Q_EMIT textChanged(someText);
}
Q_SIGNALS:
void textChanged(const QString &text);
};
class SubClass:public QObject
{
Q_OBJECT
public Q_SLOTS:
void onTextChanged(const QString &text) { //do not inline
//do something
}
};
int main()
{
QApplication a;
MainClass m;
SubClass s;
QObject::connect(&m, SIGNAL(textChanged(QString)),
&s, SLOT(onTextChanged(QString))); //const and & are removed from
//the arguments
return a.exec(); //run the event loop
}
So, there are 2 things important:
1. Signals and slots must be declared in a class derived from QObject
2. The classes containing signals and slots declarations must add the Q_OBJECT macro to the class declaration
To keep it simple for you: always declare your classes containing signals or slots in a header file (never in a .cpp file).
A very good starting point for signals and slots is: http://woboq.com/blog/how-qt-signals-slots-work.html but also the official Qt doc does it: http://qt-project.org/doc/qt-4.8/signalsandslots.html
Basically what happens: you declare some special methods (signals and slots), at the compilation phase Qt generates extra CPP files which take care of your methods (moc) then everything is compiled and linked together and at the end when Qt or someone else emits a signal it will go to the corresponding slot.
I try to explain it.
In main.h you should to declare a signal:
signals:
void textChanged(const QString& text);
And in messagelog.h you should to declare a slot:
public slots:
void updateWidgets(const QString& text);
In main.cpp you should emit this signal:
void TheMethod() {
emit this->textChanged("Your text/value");
}
In messagelog.cpp you should get this value:
// Note: Normalized signal/slot signatures drop the consts and references.
connect(&a, SIGNAL(textChanged(QString)), this, SLOT(updateWidgets(QString)));
void updateWidgets(const QString& text) {
messageLog = new QPlainTextEdit();
messageLog->setFixedHeight(500);
messageLog->setFixedWidth(600);
messageLog->setPlainText(text)
layout->addWidget(messageLog, 2,0);
}
I think it should works.
UPDATE:
Complete example: https://dl.dropboxusercontent.com/u/29647980/test.zip
What I simply want to do is connect a signal inside a thread to a slot in the main thread to handle UI changes.
This is basically the current state of my thread, nothing fancy but it's just for testing purposes atm:
// synchronizer.h
class Synchronizer : public QObject
{
Q_OBJECT
public:
Synchronizer();
signals:
void newConnection(std::wstring id);
private:
QTimer timer;
private slots:
void synchronize();
}
// synchronizer.cpp
Synchronizer::Synchronizer()
{
connect(&timer, SIGNAL(timeout()), this, SLOT(synchronize()));
timer.start();
}
void Synchronizer::synchronize()
{
emit newConnection(L"test");
}
And here's how my MainWindow looks:
// mainwindow.h
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
Synchronizer synchronizer;
private slots:
void addConnection(std::wstring id);
}
// mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(&synchronizer, SIGNAL(newConnection(std::wstring)),
this, SLOT(addConnection(std::wstring)));
QThread *thread = new QThread;
// The problems starts here?
synchronizer.moveToThread(thread);
thread->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::addConnection(std::wstring id)
{
// Add a new connection to QListWidget
ui->connectionList(QString::fromStdWString(id));
}
If I remove there lines:
synchronizer.moveToThread(thread);
thread->start();
everything seems to work as expected, that is a new item is added every second to a QListWidget but as soon as I move the synchronizer object to thread it simply stops working. I'd presume it has something to do with the connect context but I'm not really sure how something like this should be achieved as I'm quite new to Qt.
It seems that the in this case was simply the fact that I am using std::wstring as an argument in the signal without registering the type first and after adding the following line qRegisterMetaType<std::wstring>("std::wstring"); to the code, everything worked as expected.
If I would have read the output console more carefully I would have solved the problem without too much hassle as it was clearly stated that:
QObject::connect: Cannot queue arguments of type 'std::wstring'
So simply speaking, read the compiler output and don't be stupid like me :)