我正在尝试使用字符串和枚举向qstatemachine添加属性,但是machine.property()的结果为空。
QStatemachine m_machine;
idle->assignProperty(&m_machine, "state", Z_IDLE);
cool->assignProperty(&m_machine, "state", Z_COOL);
start_z->assignProperty(&m_machine, "state", Z_START);
QVariant st = m_machine->property("state");
QString s = st.toString();
我试图展示我的方法,看看它是否可行。
更新
idle = new QState();
start_z = new QState();
lock = new QState();
connect(this, SIGNAL(machine_exec()), this, SLOT(idle_exec()));
connect(this, SIGNAL(machine_exec()), this, SLOT(start_z_exec()));
connect(this, SIGNAL(machine_exec()), this, SLOT(sample_exec()));
m_machine->addState(idle);
m_machine->addState(start_z);
m_machine->addState(lock);
idle->assignProperty(m_machine, "state", Z_IDLE);
cool->assignProperty(m_machine, "state", Z_COOL);
start_z->assignProperty(m_machine, "state", Z_START);
idle->addTransition(this, SIGNAL(machineToStart()), start_z);
cool->addTransition(this, SIGNAL(machineToMotDn()), motDn);
motDn->addTransition(this, SIGNAL(machineToFini()), fini);
void MeasController::idle_exec()
{
qDebug()<<"idle_exec";
emit machineToStart();
}
void MeasController::start_z_exec()
{
qDebug()<<"start_z_exec";
QVariant s = m_machine->property("state");
qDebug()<<"property value"<<s.toString();
if (m_machine->property("state") == Z_START) {
emit machineStartToSample();
}
}
答案 0 :(得分:2)
属性仅在状态转换时分配,因此如果在启动计算机之前检查属性的值并且事件循环有机会运行,则无法获得有效值。
此外,通过多次使用该属性的名称,您可能会犯错误。您应该使用名称常量来表示属性名称,而不是字符串文字 - 它们应该足够不同,以便随机错别字被捕获。即像kStateA
和kStateB
这样的属性名称可能太相似了 - 错字会沉默。
因此,我将代码更改为:
static const char kState[] = "state";
QStatemachine m_machine;
idle->assignProperty(&m_machine, kState, Z_IDLE);
cool->assignProperty(&m_machine, kState, Z_COOL);
start_z->assignProperty(&m_machine, kState, Z_START);
qDebug() << m_machine->property(kState).toString();
另请注意,此单一当前状态的概念仅适用于您的计算机是非分层的。由QStateMachine
实施的常规HSM始终位于 set 状态中,可从configuration()
获得。最现实的机器应该是分层的,因此你的方法不会起作用。
还不清楚_exec
方法是否有用,以及用于在状态之间转换的显式信号。
以下是您可能会发现有用的方法的完整示例。
首先,让我们从如何简洁地指定您的MeasController
开始:
class MeasController : public QObject {
Q_OBJECT
QStateMachine m_machine{this};
NamedState
m_idle {"Idle", &m_machine},
m_start_z {"Start Z", &m_machine},
m_active_z{"Active Z", &m_machine},
m_downMove{"DownMove", &m_machine};
Transition
m_toStartZ{&m_idle, &m_start_z},
m_toDownMove{&m_active_z, &m_downMove};
Delay
m_d1{&m_start_z, &m_active_z, 1000},
m_d2{&m_downMove, &m_active_z, 2000};
QList<QState*> states() const { return m_machine.findChildren<QState*>(); }
public:
MeasController(QObject * parent = 0) : QObject(parent) {
for (auto state : states())
connect(state, &QState::entered, [state]{ qDebug() << state->objectName(); });
m_machine.setInitialState(&m_idle);
m_machine.start();
}
Q_SLOT void startZ() { m_toStartZ.trigger(); }
Q_SLOT void moveDown() { m_toDownMove.trigger(); }
};
这是状态机的完整规范。状态及其转换和其他行为是MeasController
的成员,因此很容易在一个地方找到。为便于调试,在输入每个状态时提供调试输出。提供插槽以触发类外部的行为,而不必暴露内部。
现在让我们看一下NamedState
,Transition
和Delay
类定义的习语。
一个命名的状态习惯用法,类似于Qt 3&#39; QObject
在构造函数中取名,对于调试至少是有用的。如果要分配状态的任何其他属性,也可以在此类中执行此操作。由于状态具有唯一的变量名称并且是父类的成员,因此任何整数标识符都是不必要的:
// https://github.com/KubaO/stackoverflown/tree/master/questions/state-properties-36745219
#include <QtWidgets>
class NamedState : public QState { Q_OBJECT
public:
NamedState(const char * name, QStateMachine * parent) : QState(parent) {
setObjectName(QString::fromUtf8(name));
}
};
转换类有助于简明地将状态机的结构指定为Transition
实例的简单成员定义:
struct Transition : public QObject { Q_OBJECT
public:
Transition(QState * source, QState * destination) : QObject(source->machine()) {
source->addTransition(this, &Transition::trigger, destination);
}
Q_SIGNAL void trigger();
};
我确定您的状态机具有更复杂的行为,但通常有助于将某个行为分解为自己的类。例如。假设你想要一个延迟后发生的状态转换:
class Delay : public Transition { Q_OBJECT
int m_delay;
QBasicTimer m_timer;
void timerEvent(QTimerEvent * ev) {
if (m_timer.timerId() != ev->timerId()) return;
m_timer.stop();
trigger();
}
public:
Delay(QState * s, QState * d, int ms) : Transition(s, d), m_delay(ms) {
connect(s, &QState::entered, this, [this]{ m_timer.start(m_delay, this);});
}
};
最后,我们可以提供一个简单的UI来测试和可视化机器的行为。为了便于使用,调试输出在QPlainTextEdit
中重复。
int main(int argc, char ** argv) {
QApplication app{argc, argv};
MeasController ctl;
QWidget w;
QGridLayout layout{&w};
QPushButton start{"Start"};
QPushButton moveDown{"Move Down"};
QPlainTextEdit log;
log.setReadOnly(true);
layout.addWidget(&start, 0, 0);
layout.addWidget(&moveDown, 0, 1);
layout.addWidget(&log, 1, 0, 1, 2);
QObject::connect(&start, &QPushButton::clicked, &ctl, &MeasController::startZ);
QObject::connect(&moveDown, &QPushButton::clicked, &ctl, &MeasController::moveDown);
static QtMessageHandler handler = qInstallMessageHandler(
+[](QtMsgType t, const QMessageLogContext& c, const QString & msg){
static QPointer<QPlainTextEdit> log{[]{
for (auto w : qApp->topLevelWidgets())
for (auto log : w->findChildren<QPlainTextEdit*>()) return log;
Q_ASSERT(false);
}()};
if (log) log->appendPlainText(msg);
handler(t, c, msg);
});
w.show();
return app.exec();
}
#include "main.moc"
这是完整的例子。 This是一个有点类似风格的较长例子。