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成为其孩子来完成。下面是这种方法所对应的状态转换图:https://s2.51cto.com/images/blog/202207/12160709_62cd2bad9b12e80719.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/format,webp/resize,m_fixed,w_1184

上面的三个状态被重命名为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就不会再有状态切换。

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

Qt状态机框架介绍(一) - 知乎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值