PDA

View Full Version : Prevent signals from "stacking up"?



jms
7th December 2014, 18:28
Hello Qt Centre,

I have an application that is split into two parts: the GUI, and a Manager object. Manager is a QObject with several slots, and when the slots are invoked, they run in a separate thread. QObject::moveToThread() makes this very easy.

Buttons on the GUI are connected to slots on the Manager. The Manager controls a piece of hardware (I don't think the specifics here are too important - what is important is the slots do not execute instantaneously).

The essence of my problem is that my users have a tendency to mash the buttons faster than the slots can execute (faster than the hardware's motors can move), causing the Manager object to repeatedly invoke a slot. This is rarely what the users want, and ultimately they have to wait for the hardware to finish performing all their unwanted actions before they can start being productive again.

OK, so the solution is simple, right? Just tell the users to not do that, problem solved! If only it were that easy :)

My solution up until this point has been for the Manager object to emit a "I am busy" signal to the GUI. A slot on the GUI loops through most of the widgets in the GUI and disables them. Then when the Manager is done executing, it emits a signal indicating that it is not busy anymore, and the GUI turns all the widgets back on. This approach has worked for a while, but it is starting to be a bit of a maintenance burden: certain widgets need to be disabled for other reasons (hardware has entered a state where certain functionality is unavailable). The code for widget enabling/disabling is starting to smell bad. I am now in search of a cleaner solution to the button mashing problem.


Ideally, if a slot on the Manager is currently executing, additional signals connected to the slot will be ignored, or perhaps two or three signals would be queued, and subsequent signals discarded until the slot is done executing.

I've done a bit of searching but I have not been able to articulate my problem very well to Google (I hope I have done an OK job of describing it here...)

Qt::BlockingQueuedConnection does not do what I need.

Have I just designed my application badly?
Is there a name for a design pattern that I can try to understand, that either does not have this problem, or solves it elegantly?
Any insight or guidance would be greatly appreciated.

Thanks in advance

wysota
7th December 2014, 19:00
My solution up until this point has been for the Manager object to emit a "I am busy" signal to the GUI. A slot on the GUI loops through most of the widgets in the GUI and disables them. Then when the Manager is done executing, it emits a signal indicating that it is not busy anymore, and the GUI turns all the widgets back on. This approach has worked for a while, but it is starting to be a bit of a maintenance burden: certain widgets need to be disabled for other reasons (hardware has entered a state where certain functionality is unavailable). The code for widget enabling/disabling is starting to smell bad. I am now in search of a cleaner solution to the button mashing problem.
Maybe it is enough to restructure the code for managing which object is enabled and which is not? It should in general be pretty easy to implement a manager which tracks the tri-state (enabled, implicitly disabled, explicitly disabled) value of the enabled property of an object.


Ideally, if a slot on the Manager is currently executing, additional signals connected to the slot will be ignored, or perhaps two or three signals would be queued, and subsequent signals discarded until the slot is done executing.

I've done a bit of searching but I have not been able to articulate my problem very well to Google (I hope I have done an OK job of describing it here...)

Qt::BlockingQueuedConnection does not do what I need.

Have I just designed my application badly?
Is there a name for a design pattern that I can try to understand, that either does not have this problem, or solves it elegantly?
Any insight or guidance would be greatly appreciated.

Why not simply queue jobs and execute them in sequence? When a job finishes execution, it checks if there are more jobs queued, takes the first one and executes.

jms
7th December 2014, 19:17
Why not simply queue jobs and execute them in sequence? When a job finishes execution, it checks if there are more jobs queued, takes the first one and executes.
I think this is what I needed to hear. I need to spend a little time thinking about it, but I think I could make that work... and it would bring some other unrelated advantages.

Thanks!

wysota
7th December 2014, 23:09
For completeness, here is an example of using a manager for enabling and disabling widgets.


#include <QtWidgets>

class EnabledManager;

class EnablableWidget : public QObject {
Q_OBJECT
Q_PROPERTY(bool enabled READ isEnabled NOTIFY enabledChanged)
public:
EnablableWidget(QWidget *widget, QObject *parent = 0) : QObject(parent) {
m_widget = widget;
connect(this, SIGNAL(enabledChanged(bool)), m_widget, SLOT(setEnabled(bool)));
}
QWidget *widget() const { return m_widget; }
bool isEnabled() const { return !(m_implicitlyDisabled || m_disabled); }

public slots:
void setDisabled(bool val = true) {
if(m_disabled == val) return;
m_disabled = val;
if(!m_implicitlyDisabled) emit enabledChanged(!m_disabled);
}
void setImplicitlyDisabled(bool val = true) {
if(m_implicitlyDisabled == val) return;
m_implicitlyDisabled = val;
if(!m_disabled) emit enabledChanged(!m_implicitlyDisabled);
}
signals:
void enabledChanged(bool);
private:
QWidget *m_widget = 0; //C++11
bool m_disabled = false; //C++11
bool m_implicitlyDisabled = false; //C++11
};

class EnabledManager : public QObject {
Q_OBJECT
public:
EnabledManager(QObject *parent = 0) : QObject(parent) {}
EnablableWidget* addWidget(QWidget *widget) {
EnablableWidget * ew = entry(widget);
if(!ew) {
ew = new EnablableWidget(widget, this);
m_widgets << ew;
}
return ew;
}
EnablableWidget *addWidget(EnablableWidget *ew) {
if(m_widgets.contains(ew)) return ew;
m_widgets << ew;
return ew;
}

EnablableWidget * entry(QWidget *widget) const {
foreach(EnablableWidget *w, m_widgets) if(w->widget() == widget) return w;
return 0;
}
public slots:
void setDisabled(bool val) {
foreach(EnablableWidget *ew, m_widgets) ew->setImplicitlyDisabled(val);
}
void setEnabled(bool val) { setDisabled(!val); }
private:
QList<EnablableWidget*> m_widgets;
};

#include "main.moc"

int main(int argc, char **argv) {
QApplication app(argc, argv);
EnabledManager manager;
QWidget w;
QVBoxLayout *l = new QVBoxLayout(&w);
QHBoxLayout *hl = new QHBoxLayout;
QCheckBox *dAll = new QCheckBox("DISABLE");
hl->addWidget(dAll);
for(int i=0;i<5;++i) {
QPushButton *pb = new QPushButton("BUTTON");
l->addWidget(pb);
EnablableWidget *ew = manager.addWidget(pb);
QCheckBox *cb = new QCheckBox(QString::number(i+1));
QObject::connect(cb, SIGNAL(toggled(bool)), ew, SLOT(setDisabled(bool)));
hl->addWidget(cb);
}
l->addLayout(hl);
QObject::connect(dAll, SIGNAL(toggled(bool)), &manager, SLOT(setDisabled(bool)));
w.show();
return app.exec();
}

"DISABLE" does an implicit disable of all registered widgets, other checkboxes explicitly disable a particular button. You could build up on it by registering expressions that explicitly disable certain widgets when particular criteria are met. QML could come in very handy here.