I've the follwoing issue.
There are two signals :
void buttonChanged(int);
void pulseWidthValue(int);
buttonChanged is emitted from a slot nextBtn:
void Program::nextBtn()
{
m_currentBtn++;
if(m_currentBtn > btnGrp->buttons().size())
{
m_currentBtn = 0;
phaseOver = true;
saveToXMLFile();
}
emit buttonChanged(m_currentBtn);
}
it's connected as follows:
connect(ui->btn_nextPhase, &QPushButton::clicked, this, &Program::nextBtn);
connect(this, &Program::buttonChanged, this, &Program::paintBtn);
the signal, buttonChanged is used in another slot to paint button:
void Program::paintBtn(int id) // how do I change the def of this function to receive to signal?
{
if(id==1)
{
ui->btn1->setStyleSheet(StyleSheetOn);
ui->btn2->setStyleSheet(StyleSheetOff);
ui->btn3->setStyleSheet(StyleSheetOff);
}
else if(id==2)
{
ui->btn1->setStyleSheet(StyleSheetOff);
ui->btn2->setStyleSheet(StyleSheetOn);
ui->btn3->setStyleSheet(StyleSheetOff);
}
else if(id==3)
{
ui->btn1->setStyleSheet(StyleSheetOff);
ui->btn2->setStyleSheet(StyleSheetOff);
ui->btn3->setStyleSheet(StyleSheetOn);
}
else
{
ui->btn1->setStyleSheet(StyleSheetOff);
ui->btn2->setStyleSheet(StyleSheetOff);
ui->btn3->setStyleSheet(StyleSheetOff);
}
ui->label_7->setText(QString::number(pw_value)); // this pw_value is from the other signal pulseWidthValue
}
the basic idea is , there are 3 phase buttons , clicking Next will switch between these buttons and change its color. Now I need to use the other signal pulseWidthValue inside paintBtn
Now I come to the question:
How do I connect two signals, buttonChanged and pulseWidthValue, (both signals are coming from different functions) to the paintBtn slot?
Qt allows you to connect multiple times even to the same slots. It even allows you to connect between signals.
Check this example, is not meant to be functional but descriptive enought to see many options and accesability.
class A:public QObject
{
Q_OBJECT
...
signals:
void signalA1();
void signalA2(const QString &);
}
class B:public QObject
{
Q_OBJECT...
signals:
void signalB();
public slots:
void slotB();
}
class C:public QObject
{
...
public:
C(QObject *parent):QObject(parent)
{
a=new A(this);
b=new B(this);
}
void connectionTest()
{
connect (a,&A::signalA1,b,&B::slotB) ; // one connection to slot B::slotB
connect (this,&C::signalC,b,&B::slotB); // another connection to B::slotB
connect (a,&A::signalA1,this,&C::slotC) ; // another connection from A::signalA, slotC is private so only I can connect
connect (b,&B::signalB,this,&C::signalC); // connection from signal to signal
connect (a,&A::signalA2,this,&C::slotC); //connection from A::signalA2 to C::slotC
//with different argument count, but compatible as slotC doesn't need an argument
connect (a,&A::signalA1,b,&B::slotB) ; // duplicate connection to slot B::slotB (signalA1 will trigger slotB two times)
}
signals:
void signalC();
private slots:
void slotC();
private:
class A *a;
class B *b;
}
Related
I'm making some GUI through QT.
I almost complete my work but I have a hard time dealing with Qthread.
My goal is to measure the position of the motor (it moves) and display it on the Qtextbrowser while working another function in the main thread. When I wrote codes like below, people said I can't use QTextBrowser(Qwidget) directly in the thread, so I'm searching how to return location value to the main thread. Can you do me a favor?
MDCE is a class in another header and the codes I attach are some parts of my first code.
void MotorPort::StartThread(MDCE* com, QTextBrowser* browser)
{
thread1 = QThread::create(std::bind(&MotorPort::MeasureLocation,this,com,browser));
thread1 -> start();
}
void MotorPort::MeasureLocation(MDCE* com, QTextBrowser* browser)
{
double location;
while(1)
{
location = CurrentLocation(com); \\return current position value
browser->setText(QString::number(location));
if (QThread::currentThread()->isInterruptionRequested()) return ;
}
}
void MotorPort::stopMeasure()
{
thread1->requestInterruption();
if (!thread1->wait(3000))
{
thread1->terminate();
thread1->wait();
}
thread1 = nullptr;
}
You should use the Qt signal/slot mechanism for iter-thread notification such as this. Firstly change your MotorPort class definition to declare a signal location_changed...
class MotorPort: public QObject {
Q_OBJECT;
signals:
void location_changed(QString location);
...
}
Now, rather than MotorPort::MeasureLocation invoking QTextBrowser::setText directly it should emit the location_changed signal...
void MotorPort::MeasureLocation (MDCE *com, QTextBrowser *browser)
{
while (true) {
double location = CurrentLocation(com);
/*
* Emit signal to notify of location update.
*/
emit location_changed(QString::number(location));
if (QThread::currentThread()->isInterruptionRequested())
return ;
}
}
Finally, update MotorPort::StartThread to connect the signal to the browser's setText slot...
void MotorPort::StartThread (MDCE *com, QTextBrowser *browser)
{
connect(this, &MotorPort::location_changed, browser, &QTextBrowser::setText);
thread1 = QThread::create(std::bind(&MotorPort::MeasureLocation, this, com, browser));
thread1->start();
}
I have a EmitSignal class like this:
class EmitSignal : public QObject
{
Q_OBJECT
public:
EmitSignal() {
emit emittedSignal();
qDebug() << "Signal emitted";
}
signals:
void emittedSignal();
};
And in ConnectSlot class, it's like this:
class ConnectSlot : public QMainWindow
{
Q_OBJECT
public:
ConnectSlot() {
connect(&emitSignalObject, &EmitSignal::emittedSignal, this, &ConnectSlot::connectToSlot);
}
EmitSignal emitSignalObject;
public slots:
void connectToSlot() {
qDebug() << "Connected";
}
};
As you can see, I tried to connect the signal and slot, but it seems like the slot is not triggered. And the only output I got is: Signal emitted.
Why isn't the slot not connected and how do I do it properly?
Thanks
You are emitting a signal from EmitSignal's constructor. That constructor will run before the body of ConnectSlot's constructor begins execution.
So the signal will be emitted before the connection is made.
You need to change your code so that connections are made before signals get fired.
I'm developing a multithreaded application, I need to instantiate n devices by modbus.
So I created a controller (ServiceSM) that instantiates N threads (ServiceSlots).
The devices are varied so I had to create "drivers" for each type of device, one of the drivers uses the QModbusClient class, so I created a controller to manage the device type.
schema
To test the operation of the state machine and connection to the device, I made an example code to run in a graphical interface.
I deleted some snippets of code that do not matter to make it easier to understand
In the MD4040driver class
When my code runs this section, the following messages appear.
If I instantiate the DeviceDriver class in the graphical interface, it works perfectly, the problem occurs when I instantiate it inside a thread.
when calls
modbusDevice->connectDevice()
MD4040drive::sm_conn() - try connect - this my message
Error:
QObject::connect: Cannot queue arguments of type
'QModbusDevice::State' (Make sure 'QModbusDevice::State' is registered
using qRegisterMetaType().)
QObject: Cannot create children for a parent that is in a different
thread. (Parent is QTcpSocket(0x24a6ce8), parent's thread is
ServiceSlots(0xea66488), current thread is QThread(0x2418a78)
QObject: Cannot create children for a parent that is in a different
thread. (Parent is QTcpSocket(0x24a6ce8), parent's thread is
ServiceSlots(0xea66488), current thread is QThread(0x2418a78)
void MD4040drive::sm_conn()
{
if (modbusDevice->state() != QModbusDevice::ConnectedState) {
modbusDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, this->cfg.modbus.porta );
modbusDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, this->cfg.modbus.ip);
modbusDevice->setTimeout( this->cfg.modbus.timeout );
modbusDevice->setNumberOfRetries(this->cfg.modbus.retries);
qDebug() << "MD4040drive::sm_conn() - try connect";
if (!modbusDevice->connectDevice()) {
qDebug() << "Erro: " << modbusDevice->errorString();
} else {
qDebug() << "Aguardando conexão...";
}
}
else{
//already connected...
this->getDados_NO_TH();
}
}
rest my code(parts)
devicedriverviewgui.h
devicedriverviewgui.cpp
class DeviceDriverViewGUI : public QDialog
{
Q_OBJECT
public:
explicit DeviceDriverViewGUI(QWidget *parent = 0);
~DeviceDriverViewGUI();
private slots:
void on_pbTry_clicked();
private:
Ui::DeviceDriverViewGUI *ui;
ServiceSlots *serviceSlot;
};
void DeviceDriverViewGUI::on_pbTry_clicked()
{
Equip equip_try = Equip();
serviceSlot = new ServiceSlots();
serviceSlot->setEquipamento(equip_try);
serviceSlot->start();
}
serviceslots.h
serviceslots.cpp
class ServiceSlots : public QThread
{
Q_OBJECT
public:
ServiceSlots();
void run();
private:
QTimer *timer;
DeviceDriver *device;
private slots:
void sm_getData();
void device_response(bool boEnd);
};
void ServiceSlots::run()
{
int e;
eventLoop = new QEventLoop();
timer = new QTimer();
connect(timer, SIGNAL(timeout()),this, SLOT(sm_controler()));
timer->start(TICK_SM_SLOT);
this->device = new DeviceDriver();
e = eventLoop->exec();
qDebug() << "Exit loop"<< e;
}
void ServiceSlots::sm_controler()
{
if(this->idleState){;}
else{
this->sm_getData();
this->idleState = true;
}
}
void ServiceSlots::sm_getData()
{
connect(device,SIGNAL(end(bool)),this,SLOT(device_response(bool)));
device->requestDeviceDriver(&this->equipamento,&this->next_date);
}
devicedriver.h
devicedriver.cpp
class DeviceDriver : public QObject
{
Q_OBJECT
public:
DeviceDriver();
void requestDeviceDriver(Equip *equip,QDateTime *date);
private:
//Drivers de dispositivos..
MD4040drive *md4040;
private slots:
//Request data to driver...
void request();
signals:
void end(bool boEnd);
};
void DeviceDriver::request()
{
connect(md4040,SIGNAL(end(bool)),this,SLOT(md4040_end(bool)));
this->md4040->requestMD4040drive(&this->equip,&this->date);
}
DeviceDriver::DeviceDriver(){
----
md4040 = new MD4040drive();
---
}
void DeviceDriver::requestDeviceDriver(Equip *equip, QDateTime *date){
this->equip = *equip;
this->date = *date;
this->request();
}
md4040drive.h
md4040drive.cpp
class MD4040drive : public QObject // QObject//public QObject QRunnable QThread
{
Q_OBJECT
public:
explicit MD4040drive(QObject *parent = 0);
~MD4040drive();
void requestMD4040drive(Equip *equip,QDateTime *date);
private:
void run();
QModbusClient *modbusDevice;
private slots:
void m_conn();
signals:
void end(bool boRun);
};
MD4040drive::MD4040drive(QObject *parent): QObject(parent)
{
modbusDevice = new QModbusTcpClient();
connect(modbusDevice, &QModbusClient::stateChanged,this, &MD4040drive::onStateChanged);
}
void MD4040drive::requestMD4040drive(Equip *equip, QDateTime *date)
{
this->equip = *equip;
this->date = *date;
this->run();
}
void MD4040drive::run()
{
this->sm_conn();
}
void MD4040drive::sm_conn()
{
if (modbusDevice->state() != QModbusDevice::ConnectedState) {
modbusDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, this->cfg.modbus.porta );
modbusDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, this->cfg.modbus.ip);
modbusDevice->setTimeout( this->cfg.modbus.timeout );
modbusDevice->setNumberOfRetries(this->cfg.modbus.retries);
qDebug() << "MD4040drive::sm_conn() - try connect";
if (!modbusDevice->connectDevice()) {
qDebug() << "Erro: " << modbusDevice->errorString();
} else {
qDebug() << "Aguardando conexão...";
}
}
else{
//already connected...
this->getDados_NO_TH();
}
}
There are a few problems:
You need to call qRegisterMetaType<QModbusDevice::State>() in main().
You need to maintain parent-child relationships between all objects that are potentially moved to other threads as a group.
The ServiceSlots and DeviceDriver classes seem to be unnecessary.
The ubiquitous this-> is non-idiomatic C++. Don't write this-> unless you need to disambiguate a member from a local variable.
Prefer to hold objects by value if they have the same lifetime as the parent object. Let the compiler generate memory management code for you!
Leverage C++11.
Fist of all, let's have a helper SafeThread class that provides us with a thread that is safely destructible at any time:
class SafeThread : public QThread {
Q_OBJECT
using QThread::run;
public:
using QThread::QThread;
~SafeThread() { quit(); wait(); }
};
The DeviceDriverViewGUI class can hold the drive and its thread by value:
class DeviceDriverViewGUI : public QDialog
{
Q_OBJECT
public:
explicit DeviceDriverViewGUI(QWidget *parent = nullptr);
private:
Ui::DeviceDriverViewGUI ui;
MD4040drive drive;
SafeThread driveThread{this};
Equip equipamento;
QDateTime nextDate;
Q_SLOT void on_pbTry_clicked();
};
Then, the pushbutton can be connected directly to the drive's thread context, and run the requestMD4040drive in the proper thread:
DeviceDriverViewGUI::DeviceDriverViewGUI(QWidget *parent) : QDialog(parent)
{
ui.setupUi(this);
// vvvvvv -- gives the thread context
connect(ui.pbTry, &QPushButton::clicked, &drive, [this]{
Q_ASSERT(QThread::currentThread() == drive.thread()); // ensured by the thread context
drive.requestMD4040drive(&equipamento, nextDate);
});
connect(&drive, &MD4040drive::end, this, [this](bool end){
//...
});
drive.moveToThread(&driveThread);
driveThread.start();
}
When done this way, you don't need any extraneous helper objects nor timers to queue requests. Qt handles all of it.
When passing Qt value classes to functions, pass them by const reference, not by pointer. The MD4040drive should look roughly as follows:
class MD4040drive : public QObject
{
Q_OBJECT
public:
explicit MD4040drive(QObject *parent = nullptr);
void requestMD4040drive(Equip *equip, const QDateTime &date);
Q_SIGNAL void end(bool boRun);
private:
Equip *equip = nullptr;
QDateTime date;
QModbusTcpClient modbusDevice{this};
Cfg cfg;
Q_SLOT void onStateChanged();
Q_SLOT void m_conn();
void sm_conn();
void getDados_NO_TH() {}
};
The implementation:
MD4040drive::MD4040drive(QObject *parent): QObject(parent)
{
connect(&modbusDevice, &QModbusClient::stateChanged,this, &MD4040drive::onStateChanged);
}
void MD4040drive::requestMD4040drive(Equip *equip, const QDateTime &date)
{
this->equip = equip;
this->date = date;
sm_conn();
}
void MD4040drive::sm_conn()
{
if (modbusDevice.state() != QModbusDevice::ConnectedState) {
modbusDevice.setConnectionParameter(QModbusDevice::NetworkPortParameter, cfg.modbus.porta );
modbusDevice.setConnectionParameter(QModbusDevice::NetworkAddressParameter, cfg.modbus.ip);
modbusDevice.setTimeout( this->cfg.modbus.timeout );
modbusDevice.setNumberOfRetries(this->cfg.modbus.retries);
qDebug() << "MD4040drive::sm_conn() - try connect";
if (!modbusDevice.connectDevice()) {
qDebug() << "Erro: " << modbusDevice.errorString();
} else {
qDebug() << "Aguardando conexão...";
}
}
else{
//already connected...
getDados_NO_TH();
}
}
The configuration class might look as follows - notice that the compiler will generate the necessary constructors and destructors for you:
struct Cfg {
struct Modbus {
int porta = 0;
QString ip = QStringLiteral("127.0.0.1");
int timeout = 1000;
int retries = 2;
} modbus;
};
Make sure 'QModbusDevice::State' is registered using qRegisterMetaType()
Means you need to call qRegisterMetaType<QModbusDevice::State>(); before connecting signal/slot that would pass this type of parameter between threads.
And/or add Q_DECLARE_METATYPE(QModbusDevice::State) macro at global scope (never understood clearly which one is actually needed, putting both works for sure...).
See this post for more details: Emitting signals with custom types does not work
I have a QVector of pointers to type X, whose items, i.e. X's own QProcesses. These processes can terminate at arbitrary time. Now, I have constructed signal-slot connection inside class X when a process ends. However, I want to propagate it to the handler class which has a QVector of X* as a member. What is an elegant way for doing this?
You can connect a signal to a signal, hiding the source signal being an implementation detail:
class MyInterface : public QObject {
Q_OBJECT
...
QProcess m_process;
public:
Q_SIGNAL void processEnded();
MyInterface(QObject * parent = 0) : QObject(parent) {
connect(&QProcess, &QProcess::finished, this, &MyInterface::processEnded);
...
}
};
The handler class can listen to these signals and do something with them.
class Handler : public QObject {
Q_OBJECT
QVector<MyInterface*> m_ifaces; // owned by QObject, not by the vector
void addInterface(MyInterface* ifc) {
ifc->setParent(this);
connect(ifc, &MyInterface::processEnded, this, [this, ifc]{
processEnded(ifc);
});
m_ifaces.append(ifc);
}
void processEnded(MyInterface* ifc) {
// handle the ending of a process
...
}
...
};
Is there a way to consume future slots (and stop them from executing) in one of many slots connected to the same signal?
My goal here is to emit a signal with a message to many QObjects and consume (stopping iteration to future slots) when the QObject in which the message belongs to finds it.
From what I understand in the Qt Documentation:
If several slots are connected to one signal, the slots will be
executed one after the other, in the order they have been connected,
when the signal is emitted.
I want to be able to stop this process from within a slot.
Suggestions?
No, there's no way to do it, and you should not think of it this way. The sender should perform the same no matter what number of slots are connected to a signal. That's the basic contract of the signal-slot mechanism: the sender is completely decoupled from, and unaware of, the receiver.
What you're trying to do is qualified dispatch: there are multiple receivers, and each receiver can process one or more message types. One way of implementing it is as follows:
Emit (signal) a QEvent. This lets you maintain the signal-slot decoupling between the transmitter and the receiver(s).
The event can then be consumed by a custom event dispatcher that knows which objects process events of given type.
The objects are sent the event in the usual fashion, and receive it in their event() method.
The implementation below allows the receiver objects to live in other threads. That's why it needs to be able to clone events.
class <QCoreApplication>
class <QEvent>
class ClonableEvent : public QEvent {
Q_DISABLE_COPY(ClonableEvent)
public:
ClonableEvent(int type) : QEvent(static_cast<QEvent::Type>(type)) {}
virtual ClonableEvent * clone() const { return new ClonableEvent(type()); }
}
Q_REGISTER_METATYPE(ClonableEvent*)
class Dispatcher : public QObject {
Q_OBJECT
QMap<int, QSet<QObject*>> m_handlers;
public:
Q_SLOT void dispatch(ClonableEvent * ev) {
auto it = m_handlers.find(ev->type());
if (it == m_handlers.end()) return;
for (auto object : *it) {
if (obj->thread() == QThread::currentThread())
QCoreApplication::sendEvent(obj, ev);
else
QCoreApplication::postEvent(obj, ev.clone());
}
}
void addMapping(QClonableEvent * ev, QObject * obj) {
addMapping(ev->type(), obj);
}
void addMapping(int type, QObject * obj) {
QSet<QObject*> & handlers = m_handlers[type];
auto it = handlers.find(obj);
if (it != handlers.end()) return;
handlers.insert(obj);
QObject::connect(obj, &QObject::destroyed, [this, type, obj]{
unregister(type, obj);
});
m_handlers[type].insert(obj);
}
void removeMapping(int type, QObject * obj) {
auto it = m_handlers.find(type);
if (it == m_handlers.end()) return;
it->remove(obj);
}
}
class EventDisplay : public QObject {
bool event(QEvent * ev) {
qDebug() << objectName() << "got event" << ev.type();
return QObject::event(ev);
}
public:
EventDisplay() {}
};
class EventSource : public QObject {
Q_OBJECT
public:
Q_SIGNAL void indication(ClonableEvent *);
}
#define NAMED(x) x; x.setObjectName(#x)
int main(int argc, char ** argv) {
QCoreApplication app(argc, argv);
ClonableEvent ev1(QEvent::User + 1);
ClonableEvent ev2(QEvent::User + 2);
EventDisplay NAMED(dp1);
EventDisplay NAMED(dp12);
EventDisplay NAMED(dp2);
Dispatcher d;
d.addMapping(ev1, dp1); // dp1 handles only ev1
d.addMapping(ev1, dp12); // dp12 handles both ev1 and ev2
d.addMapping(ev2, dp12);
d.addMapping(ev2, dp2); // dp2 handles only ev2
EventSource s;
QObject::connect(&s, &EventSource::indication, &d, &Dispatcher::dispatch);
emit s.indication(&ev1);
emit s.indication(&ev2);
return 0;
}
#include "main.moc"
If connection was in one thread, I think that you can throw an exception. But in this case you should be catch any exception during emit a signal:
try {
emit someSignal();
} catch(...) {
qDebug() << "catched";
}
But I think that it's bad idea. I'll would be use event dispatching for this.