Is it possible to mimic the functionality of an array of signals?
-
Essentially, i'm trying to write less code possible to create a remappable keypad, and i would like to give the user the possbility of changing the selected map on the fly, based on a global setting.
The global setting would be selected via a QPushButton which belongs to an autoexclusive QButtonGroup.The keypad is a 4 button hardware; once the button has been decoded the corresponding signal is emitted;
each button, button combination, short and long press, has its own signal like for examplesignals: void keypadTopLeft(); void keypadTopRight(); ...
each signal is connected to different parts of the HMI application; connections are changed based on which page of a stacked widget is currently selected.
There are many connections in place, and i'm trying to avoid rewriting all the connections like so:
/*when widget1 is displayed*/ if (keypad_map == 1) { connect (keypad, SIGNAL(keypadTopLeft()), destinationWidget1, SLOT(on_keypadTopLeft())); else if (keypad_map == 2) connect (keypad, SIGNAL(keypadTopLeft()), destinationWidget1, SLOT(on_keypadTopRight())); ... /*when widget2 is displayed*/ if (keypad_map == 1) { connect (keypad, SIGNAL(keypadTopLeft()), destinationWidget2, SLOT(on_keypadTopLeft())); else if (keypad_map == 2) connect (keypad, SIGNAL(keypadTopLeft()), destinationWidget2, SLOT(on_keypadTopRight())); /*in keypad class*/ switch(button) { case (topLeft) emit keypadTopLeft(); break; ... }
So, to avoid rewriting all the existing code, i thought of building kind of a signal lookup table, so i would need to change the name of the signal for each existing connect and emit, but i'd avoid adding if statements everywhere, would also be a mess to disconnect properly.
The lookup table would be a 2D array where one index is the global keypad mapping option, and the second index is the result of the button decoding, like this:decltype(SIGNAL(keypadTopLeft())) keypadRemapper[KEYPAD_MAP_1][TOP_LEFT] = SIGNAL(keypadTopLeft()); ... decltype(SIGNAL(keypadTopLeft())) keypadRemapper[KEYPAD_MAP_2][TOP_LEFT] = SIGNAL(keypadTopRight());
Of course this does not work, nor does trying to declare an array of signals like
signals: void signalTable[][]();
, which doesn't even compile...I tried wrapping my head around the QSignalMapper class, but while having some trouble to understand the reasoning behind it in the first place, it also seems such class would be better suited for handling signals sent by graphical things... maybe?
Let me try to explain what i understood from the docs: they read "The QSignalMapper class bundles signals from identifiable senders" and also "re-emits them with integer, string or widget parameters corresponding to the object that sent the signal", so in my case, where i have only the class Keypad inheriting from QObject, i would not be able to distinguish the button because each button would need to be an object itself, did i get this right?
-
QMetaObject for the rescue:
#include <array> class SomeClass : public QObject { Q_OBJECT std::array<const char*, 3> arrayOfSignals{"signal1", "signal2", "signal3"}; std::array<const char*, 3> arrayOfSlots{"slot1", "slot2", "slot3"}; public: SomeClass(QObject *parent = nullptr) : QObject(parent) { QObject::connect(this, &SomeClass::signal1, this, &SomeClass::slot1); QObject::connect(this, &SomeClass::signal2, this, &SomeClass::slot2); QObject::connect(this, &SomeClass::signal3, this, &SomeClass::slot3); qDebug() << "Call slots via signal array"; for(auto entry : arrayOfSignals){ QMetaObject::invokeMethod(this, entry); } qDebug() << "Call Slots vis slot array"; for(auto entry : arrayOfSlots){ QMetaObject::invokeMethod(this, entry); } } signals: void signal1(); void signal2(); void signal3(); public slots: void slot1(){qDebug() << Q_FUNC_INFO;} void slot2(){qDebug() << Q_FUNC_INFO;} void slot3(){qDebug() << Q_FUNC_INFO;} };
does this do, what you want it to?
-
@JeKK666 Why don't you use a mapping between key and method pointer? Then you just need to change this mapping if you want to remap the keys. Something like:
enum class Keys { TopLeft, TopRight, BottomLeft, BottomRight } QMap<Keys, HERE_METHOD_POINTER> mapping; connect (this, &MyClass::keypadTopLeft, [this]() { mapping[Keys::TopLeft](); }); ... connect (this, &MyClass::keypadBottomRight, [this]() { mapping[Keys::BottomRight](); });
-
@jsulm It would seem i am not able to declare a QMap which uses a pointer to a method as the T value, as in QMap<Key, T>.
I also have troubles reading your code:
- i don't understand what results putting curly braces within connect() is going to produce;
- i don't get what the square brackets around [this] mean;
- i fail to grasp this overload of connect(), which is not the standard
connect(senderObject, SIGNAL(sig()), receiverObject, SLOT(on_sig()))
Could you elaborate more?
-
@JeKK666 said in Is it possible to mimic the functionality of an array of signals?:
i don't understand what results putting curly braces within connect() is going to produce;
i don't get what the square brackets around [this] mean;Please read about lambdas in C++.
enum class Keys { TopLeft, TopRight, BottomLeft, BottomRight } typedef void (MyClass::*MethodPointer)(); QMap<Keys, MethodPointer> mapping; // I assume mapping is class member connect (this, &MyClass::keypadTopLeft, [this]() { (this->*mapping[Keys::TopLeft])(); }); ... connect (this, &MyClass::keypadBottomRight, [this]() { (this->*mapping[Keys::BottomRight])(); });
-
@JonB I wanted to retain the signal/slot model, since conceptually i connect the keypad to different Widgets, and each widget has a dedicated slot for the keypad signal to be connected to it; also, it is how this old software, tried and true, born with Qt4.8 is structured, so since it technically ain't broke, i am not going to fix it :P (code was subsequently ported to Qt 5.3.2, but never rewritten to use newer constructs).
@jsulm I always had trouble grasping how lambdas work, therefore i never used them and a can't recognize them at a glance; i followed your suggestions, and smashed my head on it a little bit longer, i think i now have a better understading.
The QMap was still giving me troubles until i read @jsulm's first answer, as i was not able to figure out the syntax i had to use for as a member function pointer.
It did, however, provide me a suggestion on how to manually implement a similar strategy, resorting to a more basic approach: since a matrix of signals is not available, i created a matrix of function pointers calledkeypadMapper
, where each function is a stupid wrapper for a call toemit signal();
.This gives me the code readability that i want while also avoiding code duplication for the connect-disconnect section in the main program:
in the keypad class, after decoding the button, i will call(this->*keypadMapper[TOP_LEFT][KP_MAP_TYPE])();
It might not be the most elegant solution, but it fits into the rest of the codebase with minimal effort on the rest of the classes, and to me also looks quite easy to read, which is a plus for maintainability.
Thanks!
-
@JeKK666 said in Is it possible to mimic the functionality of an array of signals?:
where each function is a stupid wrapper for a call to emit signal();.
You don't need that.
emit
is meaningless to the compiler, it's just for readability. Just create a pointer to the signal directly. A signal is not magic, it's a public member. The only special feature is that it's body is not written by you but by moc -
@VRonin I tried that several times over already (inspired from this): i based my assumptions on the fact that the declaration of a signal is syntactically identical to the declaration of a function prototype, save for the
signals:
keyword prepended to it, so i proceded to try and use a function pointer when passing arguments to the connect().Alas, i was never able to guess the correct syntax which would allow the darn thing to compile, i remember getting compile errors on the connect(), but could not reproduce now.
Also, that would still require me to write switch-case connects based on the currently selected keypad map, or at least switch-case assignment of the signal pointer, since when iemit
the signal, it still wouldn't accept array indexing (even more: decltype<...> threw at me all sorts of error, and i would also need a pointer for each signal) -
QMetaObject for the rescue:
#include <array> class SomeClass : public QObject { Q_OBJECT std::array<const char*, 3> arrayOfSignals{"signal1", "signal2", "signal3"}; std::array<const char*, 3> arrayOfSlots{"slot1", "slot2", "slot3"}; public: SomeClass(QObject *parent = nullptr) : QObject(parent) { QObject::connect(this, &SomeClass::signal1, this, &SomeClass::slot1); QObject::connect(this, &SomeClass::signal2, this, &SomeClass::slot2); QObject::connect(this, &SomeClass::signal3, this, &SomeClass::slot3); qDebug() << "Call slots via signal array"; for(auto entry : arrayOfSignals){ QMetaObject::invokeMethod(this, entry); } qDebug() << "Call Slots vis slot array"; for(auto entry : arrayOfSlots){ QMetaObject::invokeMethod(this, entry); } } signals: void signal1(); void signal2(); void signal3(); public slots: void slot1(){qDebug() << Q_FUNC_INFO;} void slot2(){qDebug() << Q_FUNC_INFO;} void slot3(){qDebug() << Q_FUNC_INFO;} };
does this do, what you want it to?
-
@J-Hilk Yes! I had also tried using the
QMetaObject::invokeMethod()
, but never got around to make it work: no compile time errors, just no execution of the slot whatsoever, and was never able to narrow it down to either signal emission or sig/slot connection...Anyhow, call me dumb but i find the syntax for this overall thing a lot more convoluted than a matrix of function pointers >.<'
Still, a much more elegant solution nonetheless, that's great.Thanks!
-
@JeKK666 said in Is it possible to mimic the functionality of an array of signals?:
Anyhow, call me dumb but i find the syntax for this overall thing a lot more convoluted than a matrix of function pointers >.<'
I would say, thats a debatable point :D
#include <array> class SomeClass : public QObject { typedef void (SomeClass::*SomeClassFunction)(); Q_OBJECT // std::array<const char*, 3> arrayOfSignals{"signal1", "signal2", "signal3"}; // std::array<const char*, 3> arrayOfSlots{"slot1", "slot2", "slot3"}; std::array<SomeClassFunction,3> arrayOfSignalsPointers{&SomeClass::signal1,&SomeClass::signal2, &SomeClass::signal3}; std::array<SomeClassFunction, 3> arrayOfSlotsPointers{&SomeClass::slot1, &SomeClass::slot2, &SomeClass::slot3}; public: SomeClass(QObject *parent = nullptr) : QObject(parent) { QObject::connect(this, &SomeClass::signal1, this, &SomeClass::slot1); QObject::connect(this, &SomeClass::signal2, this, &SomeClass::slot2); QObject::connect(this, &SomeClass::signal3, this, &SomeClass::slot3); // qDebug() << "Call slots via signal array"; // for(auto entry : arrayOfSignals){ // QMetaObject::invokeMethod(this, entry); // } // qDebug() << "Call Slots vis slot array"; // for(auto entry : arrayOfSlots){ // QMetaObject::invokeMethod(this, entry); // } //---- for(auto entry : arrayOfSignalsPointers){ (this->*entry)(); } for(auto entry : arrayOfSlotsPointers){ (this->*entry)(); } } signals: void signal1(); void signal2(); void signal3(); public slots: void slot1(){qDebug() << Q_FUNC_INFO;} void slot2(){qDebug() << Q_FUNC_INFO;} void slot3(){qDebug() << Q_FUNC_INFO;} };