Qt状态机框架介绍(二)

前言

上一篇博客中已经介绍了Qt状态机的基础概念和用法,文章在这里,接下来继续介绍Qt状态机的使用。

历史状态的保存和恢复

前一个示例中,我们通过一个按钮中断状态机,在此基础上,如果我们中断状态机过后想再次回到之前停下来的地方,这时候就需要使用到历史状态
历史状态是一个假想的状态,它表示了父状态上次退出时的子状态。

历史状态通常创建为想要保存的那个状态的子状态。这样在程序运行时,当状态机检测到这种状态的存在就会在父状态退出时自动记录当前的子状态。连接到历史状态的过渡实际上就是连接到状态机上次保存的子状态,状态机会自动的将过渡前移到正在的子状态。下面是执行流程:

先看一下效果图:
在这里插入图片描述

代码如下:

#include <QWidget>
#include <QState>
#include <QStateMachine>
#include <QFinalState>
#include <QHistoryState>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void onOutputMessage();

private:
    Ui::Widget *ui;
    QStateMachine * m_pStateMachine = nullptr;
    QState * m_pState1 = nullptr;
    QState * m_pState2 = nullptr;
    QState * m_pState3 = nullptr;

    QState * m_pStateParent = nullptr;
    QFinalState * m_pFinalState = nullptr;
    QHistoryState * m_pHistoryState = nullptr;
};
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    m_pStateMachine = new QStateMachine(this);

    m_pStateParent = new QState();

    m_pState1 = new QState(m_pStateParent);
    m_pState2 = new QState(m_pStateParent);
    m_pState3 = new QState(m_pStateParent);

    m_pStateParent->assignProperty(ui->label, "text", "In s1");
    m_pState1->assignProperty(ui->btn2,"pos",QPoint(20,40));
    m_pState2->assignProperty(ui->btn2,"pos",QPoint(80,40));
    m_pState3->assignProperty(ui->btn2,"pos",QPoint(120,40));

    m_pState1->addTransition(ui->btn1,SIGNAL(clicked()),m_pState2);
    m_pState2->addTransition(ui->btn1,SIGNAL(clicked()),m_pState3);
    m_pState3->addTransition(ui->btn1,SIGNAL(clicked()),m_pState1);

    m_pStateParent->setInitialState(m_pState1);

    m_pFinalState = new QFinalState();

    m_pStateParent->addTransition(ui->btn3,SIGNAL(clicked()),m_pFinalState);
    m_pStateMachine->addState(m_pStateParent);
    m_pStateMachine->addState(m_pFinalState);

    m_pStateMachine->setInitialState(m_pStateParent);

    m_pHistoryState = new QHistoryState(m_pStateParent);

    QState *s3 = new QState();
    s3->assignProperty(ui->label, "text", "In s3");
    QMessageBox *mbox = new QMessageBox(this);
    mbox->addButton(QMessageBox::Ok);
    mbox->setText("Interrupted!");
    mbox->setIcon(QMessageBox::Information);
    QObject::connect(s3, SIGNAL(entered()), mbox, SLOT(exec()));
    s3->addTransition(m_pHistoryState);
    m_pStateMachine->addState(s3);

    m_pStateParent->addTransition(ui->btn2, SIGNAL(clicked()), s3);

    m_pStateMachine->start();

}

Widget::~Widget()
{
    delete ui;
}

使用并行状态来避免过多的状态组合

当需要同步执行多个状态时,可以将状态机设置成并行状态组,进入到并行状态后,所有子状态都会同时开始运行,每个子状态的过渡都会正常执行。但是,每一个子状态都有可能退出父状态,如果这样,父状态和它所有的子状态都会结束。

在Qt状态机框架的并行机制里有一个交错语义。所有的并行操作都是在一个事件处理中独立的、原子的被执行,所以没有事件能打断并行操作。但是,事件仍然是被顺序的处理的,因为状态机本身是单线程的。举个栗子,如果有两个过渡退出同一个并行状态组,并且它们的触发条件同时被满足。在这种情况下,第二个被处理的退出事件将没有任何实际的反应,因为第一个事件已经导致了状态机从并行状态中结束。

并行状态示例:

      QState *s1 = new QState(QState::ParallelStates);
      // s11 and s12 will be entered in parallel
      QState *s11 = new QState(s1);
      QState *s12 = new QState(s1);

检测组合状态的结束

子状态可以是一个final状态;当进入一个final子状态时,父状态会发出finished() 信号。下图显示了一个组合状态s1在做了一系列的处理后进入了一个final状态:
在这里插入图片描述

当s1进入一个final子状态时,s1会自动发出finished() 信号。我们使用一个 信号过渡 来触发一个状态转换:

s1->addTransition(s1, SIGNAL(finished()), s2);

在组合状态中使用final状态对应想隐藏组合状态的内部细节来说是非常有用的。也就是说,对应外部世界来说,只需要进入这个状态,然后等待这个状态的完成信号即可。这对于构建复杂的状态机来说是一种强有力的的封装和抽象机制。但是,对应并行状态组来说,finishe()信号只有在所以的子状态都进入final状态时才会发出。

无目标状态的过渡

一个Transition并不是一定要有一个目标状态,并且没有目标状态的过渡也可以像其他过渡一样被触发。区别是当一个没有目标状态的过渡被触发时,不会导致任何状态的改变。这运行你在状态机进入某个状态时响应一个信号或事件而不必离开那个状态。

示例:

    m_pStateMachine = new QStateMachine(this);

    m_pState1 = new QState();

    QSignalTransition *trans = new QSignalTransition(ui->btn1, SIGNAL(clicked()));

    m_pState1->addTransition(trans);

    m_pStateMachine->addState(m_pState1);

    m_pStateMachine->setInitialState(m_pState1);

    QMessageBox *mbox = new QMessageBox(this);
    mbox->addButton(QMessageBox::Ok);
    mbox->setText("Interrupted!");
    mbox->setIcon(QMessageBox::Information);
    QObject::connect(trans, SIGNAL(triggered()), mbox, SLOT(exec()));

    m_pStateMachine->start();

当每次点击按钮时,都会弹出消息框,但是状态会一直停留在m_pState1,如果显示的把状态机的状态设置为s1,s1状态会结束,然后重新进入该状态。

发布了250 篇原创文章 · 获赞 453 · 访问量 48万+

猜你喜欢

转载自blog.csdn.net/luoyayun361/article/details/102665245