Qt状态机框架介绍(一)

概述

状态机,简写为FSM(Finite State Machine),状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。
简单来说,状态机,就是负责执行各种状态的切换。Qt状态机的使用场景主要针对比较复杂的界面,或者需要切换不同状态的控件,比如三态按钮,每个状态对应不同的样式,如果自己做状态管理,那就比较麻烦了。

而状态机就是解决这种问题的,Qt中的状态机框架为我们提供了很多的API和类,使我们能更容易的在自己的应用程序中集成状态动画。这个框架是和Qt的元对象系统机密结合在一起的。比如,各个状态之间的转换是通过信号触发的,状态可被配置为用来设置QObject对象的属性以及调用其方法。可以说Qt中的状态机就是通过Qt自身的事件系统来驱动的。

状态机框架中的状态图是分层的。状态可以嵌套在其他状态中,状态机的当前配置由当前活动的状态集组成。状态机有效配置中的所有状态都有一个共同的祖先。

状态机框架中的类

以下类由Qt提供,用于创建事件驱动的状态机。

在这里插入图片描述

一个简单的示例

接下来直接通过一个简单的示例来了解Qt状态机的工作方式。

最基础的Qt状态机使用流程如下:

  • 创建一个状态机QStateMachine和需要的状态QState
  • 使用QState::addTransition() 函数为这些状态之间添加过渡
  • 将状态添加到状态机进行管理,并为状态机设置一个初始状态
  • 启动状态机

效果图:

在这里插入图片描述
这里btn2有三种状态,分别显示在不同的位置,通过点击Btn1进行状态的切换。

其状态图如下:

接下来看看代码:

#include <QWidget>
#include <QState>
#include <QStateMachine>

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;
};

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    m_pStateMachine = new QStateMachine(this);

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

    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_pStateMachine->addState(m_pState1);
    m_pStateMachine->addState(m_pState2);
    m_pStateMachine->addState(m_pState3);
    m_pStateMachine->setInitialState(m_pState1);

    m_pStateMachine->setInitialState(m_pState1);
    m_pStateMachine->start();
}

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

这样状态机就开始异步的运行了,也就是说,它成为了我们应用程序事件循环的一部分。这也对应了我们上面说的,Qt的状态机是通过Qt自身的事件机制来驱动的。

状态转换时操作QObject对象

使用QState::assignProperty() 函数当进入某个状态时让其去修改某个QObject对象的属性。也就是上面使用的m_pState1->assignProperty(ui->btn2,"pos",QPoint(20,40)); 执行该状态时,改变Btn2的位置属性。

除了操作QObject对象的属性外,我们还能通过状态的转换来调用QObject对象的函数。这是通过使用状态转换时发出的信号完成的。其中,当进入某个状态时会发出QState::enterd() 信号,当退出某个状态时会发出QState::exited() 信号。

    connect(m_pState3,&QState::entered,this,[=](){
        qDebug() << __FUNCTION__ << "state3 entered..";
    });
    connect(m_pState3,&QState::exited,this,[=](){
        qDebug() << __FUNCTION__ << "state3 exited..";
    });

当进入和离开m_pState3状态时,将会输出响应的日志。

状态机结束

以上的示例状态机是永远不会停止的,每次点击按钮会一直循环切换不同状态,那现在如果要停止状态机该怎么办呢。

为了使一个状态机在某种条件下结束,我们需要创建一个顶层的final 状态(QFinalState object) 。当状态机进入一个顶层的final 状态时,会发出finished() 信号后结束。所以,我们只需要为上面的状态图引入一个final 状态,并把它设置为某个过渡的目标状态即可。这样当状态机在某种条件下转换到该状态时,整个状态机结束。

如果我们想让用户随时通过点击退出按钮来退出整个应用程序。为了实现这个需求,我们需要创建一个final状态并使他成为和按钮的clicked()信号相关联的那个过渡的目标状态。有两种方案:

  • 方法一:为状态s1,s2,s3分别添加一个到final状态的过渡,但这看上去有点多余,代码臃肿,并且不利于将来的扩张。
  • 方法二:将状态s1,s2,s3分成一组。我们通过创建一个新的顶层状态并使s1,s2,s3成为其孩子来完成。下面是这种方法所对应的状态转换图:

上面的三个状态被重命名为s11,s12,s13以此来表明它们是s1的孩子。子状态会隐式的继承父状态的过渡。这意味着我们目前可以只添加一个s1到final状态s2的过渡即可,s11,s12,s13会继承这个过渡,从而无论在什么状态均可退出应用程序。并且,将来新添加到s1的新的子状态也会自动继承这个过渡。

所谓的分组,就是只需在创建状态时为其指定一个合适的父状态即可。当然,还需要为这组状态指定一个初始状态,即当s1是某个过渡的目标状态时,状态机应该进入哪个子状态。实现代码如下:

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

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;
};
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>

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_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();
    
	//当点击Btn3时,停止状态机
    m_pStateParent->addTransition(ui->btn3,SIGNAL(clicked()),m_pFinalState);
    m_pStateMachine->addState(m_pStateParent);
    m_pStateMachine->addState(m_pFinalState);

    m_pStateMachine->setInitialState(m_pStateParent);

    connect(m_pState3,&QState::entered,this,[=](){
        qDebug() << __FUNCTION__ << "state3 entered..";
    });
    connect(m_pState3,&QState::exited,this,[=](){
        qDebug() << __FUNCTION__ << "state3 exited..";
    });

    connect(m_pStateMachine,&QStateMachine::finished,this,[=](){
        qDebug()  << __FUNCTION__ ;
    });

    m_pStateMachine->start();
}

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

效果图如下:

在这里插入图片描述
以上示例,当点击Btn3时,停止状态机。这时候再点击Btn1就不会再有状态切换。

以上是基础的状态机使用演示。

参考地址:https://doc.qt.io/qt-5/statemachine-api.html#?tdsourcetag=s_pctim_aiomsg

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

猜你喜欢

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