I am building a program with a largely sequential flow but also some alternative paths. I thought that a state machine might be the simplest way of implementing this since Qt provides such a class: QStateMachine (also see API).
However, I seem to have quite a number of states (20+). Also, I have a number of different transition events (let's say buttons 1-10). So e.g. pressing button x would cause a transition of state 13 to 14.
Entering or leaving each state should be able to execute specific functions with parameters, and while each state emits such signals, it is not possible to pass parameters, so that it requires a potentially large number of paramter-less functions.
Reimplementing QAbstractState also seems tedious for this matter, unless it would have methods similar to assignProperty() which allows setting QObject properties on "state-entry".
Is QSignalMapper along with several Signal-Slot-Connections for each state's transition signals to handle multiple actions an appropriate approach?
If you're using C++11, you can connect directly to a lambda that then invokes your function with a specified parameter.
Otherwise, figure out what object is the sender() of your signal, and set a dynamic property on that object. That property can be queried in the slot, and passed on to the function as a parameter.
For example (within a class):
void setup() {
QState *s2 = new QState();
s2->assignProperty(myLabel, "text", "state two");
s2->setProperty("prop", 0);
connect(s2, SIGNAL(entered()), io, SLOT(testSlot()));
}
Q_SLOT void testSlot() {
QObject *obj = this->sender();
QVariant prop = obj->property("prop");
qDebug() << __FUNCTION__ << prop.toString();
}
Related
I have a QAction which I've assigned multiple shortcuts to it
test = new QAction();
this->addAction(test);
QList<QKeySequence> shortcuts;
shortcuts << QKeySequence(Qt::Key_N) << QKeySequence(Qt::Key_T);
test->setShortcuts(shortcuts);
connect(test,SIGNAL(triggered()),this,SLOT(SomeFucntion()))
In SomeFucntion I need to know which shortcut was pressed....Is there anyway of knowing that ?
You could try a more elaborate pattern with QSignalMapper that avoids the need to define as many actions as many shortcut you need, but requires c++11 (at least this implementation).
In the constructor of your window use the following code to declare your QShortcut objects and a QSignalMapper:
QSignalMapper* signalMapper = new QSignalMapper(this);
QShortcut* sc1 = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_N), this);
QShortcut* sc2 = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_T), this);
connect(sc1, &QShortcut::activated, signalMapper, static_cast<void (QSignalMapper::*)(void)>(&QSignalMapper::map));
connect(sc2, &QShortcut::activated, signalMapper, static_cast<void (QSignalMapper::*)(void)>(&QSignalMapper::map));
signalMapper->setMapping(sc1, sc1);
signalMapper->setMapping(sc2, sc2);
QAction* action = new QAction();
connect(signalMapper, static_cast<void (QSignalMapper::*)(QObject*)>(&QSignalMapper::mapped),
[action](QObject *object){
QShortcut* sc = qobject_cast<QShortcut*>(object);
if (sc)
{
action->setData(sc->key().toString());
action->trigger();
}
});
connect(action, &QAction::triggered, this, &MainWindow::doStuff);
The 3rd connection is required because of the way QSignalMapper works: when a shortcut is activated, it will be notified to the QSignalMapper thanks to the 1st and 2nd connections, that will trigger the map() slot.
The QSignalMapper::map() slot will scan its mappings, made with the setMapping() API, whose first argument is the mapped object, and the second one is the parameter that will be used to emit the mapped() slot of the QSignalMapper, once the emitting object is identified. To do so, it uses the sender() method and simply compares the pointer returned to the mapped QObject pointers you provided as mappings.
Once the QObject is identified, QSignalMapper will emit the QSignalMapper::mapped(QObject*) signal, whose argument is the second argument given to setMapping, and in this case it the same as the first one, that is again a pointer to the QShortcut that was activated.
I used a lambda to catch this signal, and inside this lambda I simply check that the parameter given is a QShortcut pointer, and store its key sequence inside the data member of the QAction before triggering the action itself. The QAction::trigger() slot will then emit the QAction::triggered() signal that will in turn invoke your custom slot, in this case doStuff(). There you can retrieve the key sequence and do what you want with it.
So your slot implementation should look similar to this one:
void MainWindow::doStuff()
{
// use sender() to fetch data from action
QAction* act = qobject_cast<QAction*>(sender());
if (act)
{
QString sequence = act->data().toString();
// debug output will show you the triggering key sequence
qDebug() << sequence;
// use sequence string to determine which shortcut was used
// On Mike hint: better to reset data after use :)
act.setData(QVariant());
}
}
Note that I'm using a mapping based on QObject pointers. In this way you can reuse the signalMapper instance to connect events from other kind of QObjects (e.g. QPushButtons) and identify them in your custom slot as well setting a proper value for the QAction data member, that can store a generic QVariant istance.
Also when using QShortcut, be aware of their contex, that is when they are active, as it could be at widget or a window scope.
Unfortunately this pattern violates pure oop principles, but could be better than managing many actions (icon, text, tooltip etc...) for the same purpose.
EDIT: to answer comments
First of all, let me clarify that you can of course skip the use of QSignalMapper at all. This is just a possible solution (not the better, maybe an overkill... but not really worse in terms of performance).
A simpler way, as pointed by Mike in the comments consists in using lambdas for each QShotcut::activated signal, but this will result in copy/paste code, that I always try to avoid.
You can instead define a custom slot inside the MainWindow and use sender() to catch the QShortcut and prepare the action before triggering it.
Anyway, QSignalMapper IMHO, better explains what you are doing (from a semantic point of view) and is more flexible in case you need to expand the connection to other QObjects, supporting also other type of mappings.
Furthermore, but this is related to my personal taste, I like the idea to have fragments of code that are logically tied condensed into small snippets, instead of have it sparse among several slot/functions because it makes it easier to read and to trace back when I need to change it, of course only if this does not hurt the quality of code itself.
You should create a separate QAction for each shortcut and group them using a QSignalMapper.
Qt’s signal and slot mechanism works fine when you have events that occur in one component and need to be handled by one or more other components.
My situation is that an event can occur in either of two classes and it needs to be handled by each of those classes (as well as by a couple of others). For example, suppose that I’m writing a modal text editor. The mode can be changed by the user (by pressing a button on a toolbar) or by the application (when a new file is opened). I might have
// Toolbar.h
signals:
void user_changed_mode(EditingMode new_mode);
// connected to AppController::user_changed_mode
public slots:
void mode_changed(EditingMode new_mode);
// AppController.h
signals:
void mode_changed(EditingMode new_mode);
// connected to Toolbar::mode_changed
public slots:
void user_changed_mode(EditingMode new_mode);
It just seems awkward to me to have two signals that convey the same information but which have different names (and likewise for the slots). Is there a simple way to use the signals and slots mechanism when the same event can originate from multiple places?
Just remember that the signals and slots can have any name that is a valid C++ identifier, and that their scope is the class in which you declare them.
So, the signals and slots in multiple classes can have the same name as long as these names are meaningful and not misleading.
But, there's another problem. You most likely have a change loop, and your code will crash due to infinite recursion. When the toolbar's mode is changed, it must emit a mode_changed signal, otherwise you're breaking the typical semantics such code should have. So, assume that the controller emits a mode changed signal, then the toolbar receives it, changes its mode, and emits a confirmatory signal, the controller does the same, and so it goes forever.
The way to break such loops is to distinguish the signal triggered at the first invocation from the signals subsequently emitted as results of such a change. You can use a discriminating boolean, or re-purpose Qt::ItemDataRole, with Qt::EditRole for the source of the change, and Qt::DisplayRole for all subsequent indications of the change - that's how you'd break the property binding loops when using QML and models.
Thus:
enum class EditingMode { ... };
Q_DECLARE_METATYPE(EditingMode)
class Toolbar : public ... {
Q_OBJECT
public:
Q_SIGNAL void modeChanged(EditingMode, Qt::ItemDataRole role = Qt::DisplayRole);
Q_SLOT void setMode(EditingMode new_mode, Qt::ItemDataRole role = Qt::EditRole) {
... // perform mode changes
if (role == Qt::EditRole) emit modeChanged(new_mode);
}
...
};
class AppController : public ... {
Q_OBJECT
public:
Q_SIGNAL void modeChanged(EditingMode, Qt::ItemDataRole role = Qt::DisplayRole);
Q_SLOT void setMode(EditingMode new_mode, Qt::ItemDataRole role = Qt::EditRole) {
... // perform mode changes
if (role == Qt::EditRole) emit modeChanged(new_mode);
}
...
};
int main(int argc, char ** argv) {
...
Toolbar toolbar1, toolbar2;
AppController controller;
for (auto toolbar : QList<Toolbar*>() << &toolbar1 << &toolbar2) {
QObject::connect(toolbar, &Toolbar::modeChanged, &controller, &Controller::setMode);
QObject::connect(&controller, &Controller::modeChanged, toolbar, &Toolbar::setMode);
}
...
toolbar1.setMode(Mode1);
// toolbar2 gets notified, but doesn't notify anyone else again
// controller gets notified, but doesn't notify anyone else again
...
controller.setMode(Mode2);
// toolbar1 gets notified, but doesn't notify the controller again
// toolbar2 gets notified, but doesn't notify the controller again
}
Unfortunately, Qt's own Widget-module controls don't follow such a pattern, and hilarity ensues when you try to link multiple controls to follow each other... Some offer special signals that only get emitted when the user modifies the data, but not all do that, and extra signals are harder to deal with...
When you use Qt Quick, everything is fine as long as you follow this or a similar pattern.
You can connect multiple signals to the same slot. Somewhere in your application initialization you just connect the signals from each object to the same slot:
connect(object1, SIGNAL(object1_changed_mode(EditingMode)),
receiverInAppController, SLOT (mode_changed(EditingMode)) );
connect(object2r, SIGNAL(object2_changed_mode(EditingMode)),
receiverInAppController, SLOT(mode_changed(EditingMode)) );
Also note that Qt5 has an alternative simplified syntax. Qt5 remains compatible with the above notation, though.
Finally, the signals can also have the same name. The only requirement is that each signal is declared individually in the class definition of each object.
I have some dynamically added QWidgets and I want to carry out some task when they are changed.
I think I can't use connect() because I also need the name of the QWidget that triggered the change.
How can I also see which QWidget was changed and still catch the value change event with a common handler?
The quick-and-dirty way is to use connect() as usual, and then in your slot method, call sender() to find out which object sent the signal. For example:
// slot method that you've connected all of your widgets' stateChanged(int) signals to
void MyClass :: someWidgetsStateChanged(int newState)
{
const QObject * s = sender();
if (dynamic_cast<const QCheckBox *>(s) == _myFirstCheckbox) printf("First checkbox is now %s\n", newState?"Checked":"unchecked");
else if (dynamic_cast<const QCheckBox *>(s) == _mySecondCheckbox) printf("Second checkbox is now %s\n", newState?"Checked":"unchecked");
[... and so on...]
}
Note that the reason this is considered "dirty" is that it breaks encapsulation. In particular, the someWidgetsStateChanged() method above now behaves differently depending on which object generated the signal, and so if e.g. at some point in the future you connected a QPushButton::clicked() (or whatever) to that same slot, you'd probably have to update the someWidgetsStateChanged() implementation to handle it appropriately. Still, this works and doesn't require a lot of effort to implement.
Use this to catch events before they are passed to QObject subclass instances:
http://qt-project.org/doc/qt-4.8/qobject.html#installEventFilter
After some additional thinking I arrived at saying why not to extend these controllers?
So that I could hook them to the parent object using parent() or using a custom constructor.
It requires potentially though that I define them as friend classes...
I'm trying to implement a state-machine in Qt (C++).
How can I check the current state of the QStateMachine?
I couldn't find a method in the documentation.
thx
have you tried QStateMachine::configuration() ?
refer http://www.qtcentre.org/threads/42085-How-to-get-the-current-state-of-QStateMachine
Excerpt from the above url:
// QStateMachine::configuration() gives you the current states.
while(stateMachine->configuration().contains(s2))
{
//do something
}
You can assign the property to the QStateMachine itself.
// QState m_State1;
// QState m_State2;
// QStateMachine m_Machine;
m_State1.assignProperty(m_Label, "visible", false);
m_State1.assignProperty(&m_Machine, "state", 1);
m_State2.assignProperty(m_Label, "visible", true);
m_State2.assignProperty(&m_Machine, "state", 2);
Then, the current state can be read from dynamic property.
qDebug() << m_Machine.property("state");
From Qt 5.7 Documentation
QSet QStateMachine::configuration() const
Returns the maximal consistent set of states (including parallel and final states) that this state machine is currently in. If a state s is in the configuration, it is always the case that the parent of s is also in c. Note, however, that the machine itself is not an explicit member of the configuration.
Example usage:
bool IsInState(QStateMachine& aMachine, QAbstractState* aState) const
{
if (aMachine_.configuration().contains(aState)) return true;
return false
}
I realize I'm coming in late, but hopefully this answer helps anyone else who stumbles across this.
You mentioned above that you already tried to use configuration(), but none of your states were there--this is because start() is asynchronous.
So, assuming you called configuration() immediately after calling start(), it makes sense that your states weren't there yet. You can get the functionality you want by using the started() signal of the QStateMachine class. Check it out:
stateMachine->setInitialState(someState);
stateMachine->start();
connect(stateMachine, SIGNAL(started()), this, SLOT(ReceiveStateMachineStarted()));
Then, for your ReceiveStateMachineStarted() slot, you could do something like this:
void MyClass::ReceiveStateMachineStarted() {
QSet<QAbstractState*> stateSet = stateMachine->configuration();
qDebug() << stateSet;
}
When your state machine enters its initial state, it will emit the start() signal. The slot you've written will hear that and print the config. For more on this, see the following Qt documentation:
http://doc.qt.io/qt-5/qstatemachine.html#started
I have two instances of QObject subclasses and two QMetaMethod instances of signal in one of objects and slot in another object. I want to connect this signal and slot with each other.
I've looked through the qobject.h file and find that SIGNAL() and SLOT() macro are just add "1" or "2" character to the beginning of method signature so it looks like it should be possible to add the same character to the beginning of string returned by QMetaMethod::signature() but this approach depends on some undocumented internals of toolkit and may be broken at any time by a new version of Qt.
Does anybody know reliable way to connect signals and slots through their QMetaMethod reflection representation?
Edited:
I've created suggestion in Qt issue tracker:
https://bugreports.qt.io/browse/QTBUG-10637
If anybody also interested in this feature you can vote for this ticket there.
This has been fixed as of Qt 4.8.0:
https://bugreports.qt.io/browse/QTBUG-10637
Suppose we have a QObject* m_subject, and wish to connect the change-notification signal of a property to a propertyChanged() slot:
const QMetaObject* meta = m_subject->metaObject();
QMetaProperty prop = meta->property(meta->indexOfProperty("myProperty"));
if (prop.hasNotifySignal()) {
QMetaMethod signal = prop.notifySignal();
QMetaMethod updateSlot = metaObject()->method(
metaObject()->indexOfSlot("propertyChanged()"));
connect(m_subject, signal, this, updateSlot);
}
I successfully used this to make a QWidget subclass which finds all the properties of any QObject and creates a QLineEdit for each of them, with a connection to keep the QLineEdit updated whenever the corresponding property changes. (Because I didn't find a way to pass a propertyID value to propertyChanged() though, it was necessary to make a subclass of QLineEdit and implement propertyChanged() there. QSignalMapper didn't help, because all the properties are in the same object.)
Thanks to MBack, I now use metamethods to connect my view to my model's properties dynamically for MVVM or MVC pattern.
In order to respect DRY, a boilerplate is required with something like this :
void MyClass::connectSignalToSlot(QObject* sender, std::string signalName, QObject* receiver, std::string slotName)
{
int sigIdx = sender->metaObject()->indexOfSignal(signalName.c_str());
auto signal = sender->metaObject()->method(sigIdx);
int slotIdx = receiver->metaObject()->indexOfSlot(slotName.c_str());
auto slot = receiver->metaObject()->method(slotIdx);
connect(sender,signal,receiver,slot);
}
void MyClass::connectPropertyChangedToSlot(QObject* sender, std::string propName, QObject* receiver, std::string slotName)
{
int sigIdx = sender->metaObject()->indexOfProperty(propName.c_str());
auto signal = sender->metaObject()->property(sigIdx ).notifySignal();
int slotIdx = receiver->metaObject()->indexOfSlot(slotName.c_str());
auto slot = receiver->metaObject()->method(slotIdx);
return connect(sender, signal, receiver, slot);
}
It looks like there is no way to make it work without relying on internal implementation. If I were you, I'd submit feature request to Qt bug tracker, write a code that mimics current behavior SIGNAL/SLOT macros and add unit test that will fail when SIGNAL/SLOT behavior changes.
There might be a simpler solution to the problem you're trying to solve: describe what exactly are you trying to do without any implementation details.
If signature method is public in QMetaMethod then the result shouldn't be broken by trolls and it's safe to use it (documentation says nothing about "dangers" when using QMetaMethod::signature method). I think you can safely use it. Just to be sure, what version of Qt you are using right now ?