QState :: assignProperty不起作用

时间:2016-04-20 13:22:28

标签: c++ qt qt5

我正在尝试使用字符串和枚举向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();
            }
    }

1 个答案:

答案 0 :(得分:2)

属性仅在状态转换时分配,因此如果在启动计算机之前检查属性的值并且事件循环有机会运行,则无法获得有效值。

此外,通过多次使用该属性的名称,您可能会犯错误。您应该使用名称常量来表示属性名称,而不是字符串文字 - 它们应该足够不同,以便随机错别字被捕获。即像kStateAkStateB这样的属性名称可能太相似了 - 错字会沉默。

因此,我将代码更改为:

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的成员,因此很容易在一个地方找到。为便于调试,在输入每个状态时提供调试输出。提供插槽以触发类外部的行为,而不必暴露内部。

现在让我们看一下NamedStateTransitionDelay类定义的习语。

一个命名的状态习惯用法,类似于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中重复。

screenshot of the example

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是一个有点类似风格的较长例子。