c++ - QState::assignProperty 不起作用

标签 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();
            }
    }

最佳答案

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

此外,如果多次使用属性名称,您可能会犯错字。您应该为属性名称使用命名常量,而不是字符串文字——并且它们应该足够不同,这样随机拼写错误就会被捕获。 IE。 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 的 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是一个有点类似风格的更长的例子。

关于c++ - QState::assignProperty 不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36745219/

相关文章:

python - pyqt5 qml 最小示例在退出时崩溃

c++ - 在 qglwidget 的顶部添加按钮

qt - 如果鼠标在鼠标释放前没有移动,则在 Qt 5 中获取鼠标放置事件

c++ - 将对象从一个 Boost ptr_container 移动到另一个

c++ - 使用 Valgrind 调试 Use After Free

c++ - 通过头文件函数将二叉搜索树中遍历的数据加载到Vector中

c++ - 为 "Humble Dialogs"创建 View 的方法

c++ - 遍历 QLineSeries 项目及其点

c++ - 达到限制后自动旋转值的自定义 qt spinbox

c++ - 使用 makefile 识别和减少耦合