Qt 状态机框架:The State Machine Framework (一)

传送门:
Qt 状态机框架:The State Machine Framework (一)
Qt 状态机框架:The State Machine Framework (二)

一、什么是状态机框架

状态机框架提供了用于创建和执行状态图/表[1]的类。这些概念和表示法基于HarelStatecharts:一种复杂系统的可视化形式,也是UML状态图的基础。状态机执行的语义是基于状态图XML(SCXML)的。

状态图提供了一种图形化的方式来建模 系统对刺激的反应。这是通过定义系统可能处于的状态,以及系统如何从一种状态移动到另一种状态(状态之间的转换)来实现的。事件驱动系统(如Qt应用程序)的一个关键特征是,行为通常不仅取决于上一个或当前事件,还取决于之前的事件。使用状态图,这些信息很容易表达。

Qt状态机框架提供了一个API和执行模型,可用于在Qt应用程序中有效地嵌入状态图的元素和语义。该框架与Qt的元对象系统紧密集成;例如,状态之间的转换可以由信号触发,并且可以将状态配置为在{QObject}s上设置属性和调用方法。Qt的事件系统用于驱动状态机

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

二、状态机框架中的核心类

下面这些类被Qt用来创建基于事件驱动的状态机。

ClassDescription
QAbstractStateThe base class of states of a QStateMachine
QAbstractTransitionThe base class of transitions between QAbstractState objects
QEventTransitionQObject-specific transition for Qt events
QFinalStateFinal state
QHistoryStateMeans of returning to a previously active substate
QKeyEventTransitionTransition for key events
QMouseEventTransitionTransition for mouse events
QSignalTransitionTransition based on a Qt signal
QStateGeneral-purpose state for QStateMachine
QStateMachineHierarchical finite state machine
QStateMachine::SignalEventRepresents a Qt signal event
QStateMachine::WrappedEventInherits QEvent and holds a clone of an event associated with a QObject

三、一些状态机的使用示例

3.1 简单3态循环状态机

有一个状态机包含3个状态:s1s2s3,这个状态机由一个按钮控制。当按钮被点击,状态机移动到另一个状态。初始状态被设置为s1.

【状态移动图】:
这台机器的状态转移图如下所示:
在这里插入图片描述
【codes】:

#include <QApplication>
#include <QPushButton>
#include <QStateMachine>
#include <QState>
#include <QDebug>
#include <QLabel>int main(int argc, char *argv[])
{QApplication a(argc, argv);QWidget w;QPushButton* button = new QPushButton(QStringLiteral("状态机测试"),&w);QLabel* label = new QLabel(QStringLiteral("状态提示"),&w);button->setGeometry(20,20,200,100);label->setGeometry(20,130,200,100);QStateMachine machine;QObject::connect(&machine,&QStateMachine::runningChanged,[&](bool running){qDebug() << "the machine is running? " << running;});QState* s1 = new QState();QState* s2 = new QState();QState* s3 = new QState();// 设置不同状态下,指定对象的属性值s1->assignProperty(label,"text","In state s1");s2->assignProperty(label,"text","In state s2");s3->assignProperty(label,"text","In state s3");// 绑定指定状态的进入和退出动作QObject::connect(s3, &QState::entered, button, [&](){w.showMaximized();});QObject::connect(s3, &QState::exited, button, [&](){w.showNormal();});// 状态转移s1->addTransition(button,SIGNAL(clicked(bool)),s2);s2->addTransition(button,SIGNAL(clicked(bool)),s3);s3->addTransition(button,SIGNAL(clicked(bool)),s1);// 为状态机添加状态machine.addState(s1);machine.addState(s2);machine.addState(s3);// 初始化状态机machine.setInitialState(s1);// 启动状态机machine.start();w.show();return a.exec();
}

【运行效果】:
在这里插入图片描述

3.2 含中止状态的 嵌套状态机 示例

假使我们需要在点击[退出]按钮时,能立即退出应用程序,不用管当前应用处于那种状态。

三个原始状态已被重命名为s11s12s13,以反映它们现在是新的顶级状态s1的子级。子状态隐式继承其父状态的转换。这意味着现在添加从s1到最终状态s2的单个转换就足够了。添加到s1的新状态也将自动继承此转换。

在这里插入图片描述
【codes】:

#include "widget.h"#include <QApplication>
#include <QState>
#include <QStateMachine>
#include <QFinalState>#include <QPushButton>
#include <QLabel>int main(int argc, char *argv[])
{QApplication a(argc, argv);QWidget w;QPushButton* button = new QPushButton(QStringLiteral("状态机测试"),&w);QPushButton* quitButton = new QPushButton(QStringLiteral("退出"),&w);QLabel* label = new QLabel(QStringLiteral("状态提示"),&w);button->setGeometry(20,20,200,100);quitButton->setGeometry(20,130,200,100);label->setGeometry(20,260,200,100);QStateMachine machine;QState* s1 = new QState();{   /// s1 子状态QState* s11 = new QState(s1);QState* s12 = new QState(s1);QState* s13 = new QState(s1);///  状态转移s11->addTransition(button,SIGNAL(clicked(bool)),s12);s12->addTransition(button,SIGNAL(clicked(bool)),s13);s13->addTransition(button,SIGNAL(clicked(bool)),s11);s11->assignProperty(label,"text","In state s11");s12->assignProperty(label,"text","In state s12");s13->assignProperty(label,"text","In state s13");// 绑定指定状态的进入和退出动作QObject::connect(s13, &QState::entered, button, [&](){w.showMaximized();});QObject::connect(s13, &QState::exited, button, [&](){w.showNormal();});// 指定s1 的初始子状态时s11s1->setInitialState(s11);}QFinalState* s2 = new QFinalState();// 当退出按钮被点击时,顶层状态移动到 s2——结束状态s1->addTransition(quitButton,SIGNAL(clicked(bool)),s2);// 当移动到结束状态时,状态机会发送finished信号,绑定信号和退出应用的槽函数,达到点击退出按钮,程序结束的目的QObject::connect(&machine, SIGNAL(finished()),QApplication::instance(), SLOT(quit()));//! 将top-level state :s1和s2 添加到状态机machine.addState(s1);machine.addState(s2);machine.setInitialState(s1);machine.start();w.show();return a.exec();
}

【运行效果】:
在这里插入图片描述

3.3 含中断的状态机 示例

想象一下,我们想在上一节讨论的例子中添加一个“中断”机制;用户应该能够点击按钮以使状态机执行一些不相关的任务,之后状态机应该恢复它之前正在做的任何事情(即,返回到旧状态,在这种情况下是s11s12s13中的一个)。
这样的行为可以很容易地使用历史状态进行建模。历史状态(QHistoryState对象)是一种伪状态,表示父状态上次退出时所处的子状态。
历史状态被创建为我们希望记录当前子状态的状态的子状态;当状态机在运行时检测到这种状态的存在时,它会在父状态退出时自动记录当前(真实)子状态。向历史状态的转换实际上是向状态机先前保存的子状态的转换;状态机自动将转换“转发”到真正的子状态。
下图显示了添加中断机制后的状态机。
在这里插入图片描述
【codes】:

#include "widget.h"#include <QApplication>
#include <QState>
#include <QStateMachine>
#include <QFinalState>
#include <QHistoryState>#include <QPushButton>
#include <QLabel>
#include <QMessageBox>int main(int argc, char *argv[])
{QApplication a(argc, argv);QWidget w;QPushButton* button = new QPushButton(QStringLiteral("状态机测试"),&w);QPushButton* quitButton = new QPushButton(QStringLiteral("退出"),&w);QPushButton* interruptButton = new QPushButton(QStringLiteral("中断"),&w);QLabel* label = new QLabel(QStringLiteral("状态提示"),&w);button->setGeometry(20,20,200,100);quitButton->setGeometry(20,130,200,100);interruptButton->setGeometry(20,260,200,100);label->setGeometry(20,400,200,100);QStateMachine machine;QState* s1 = new QState();{   /// s1 子状态QState* s11 = new QState(s1);QState* s12 = new QState(s1);QState* s13 = new QState(s1);///  状态转移s11->addTransition(button,SIGNAL(clicked(bool)),s12);s12->addTransition(button,SIGNAL(clicked(bool)),s13);s13->addTransition(button,SIGNAL(clicked(bool)),s11);s11->assignProperty(label,"text","In state s11");s12->assignProperty(label,"text","In state s12");s13->assignProperty(label,"text","In state s13");// 绑定指定状态的进入和退出动作QObject::connect(s13, &QState::entered, button, [&](){w.showMaximized();});QObject::connect(s13, &QState::exited, button, [&](){w.showNormal();});// 指定s1 的初始子状态时s11s1->setInitialState(s11);}// 当退出按钮被点击时,顶层状态移动到 s2——结束状态QFinalState* s2 = new QFinalState();s1->addTransition(quitButton,SIGNAL(clicked(bool)),s2);QObject::connect(&machine, SIGNAL(finished()),QApplication::instance(), SLOT(quit()));/// 中断状态---------------------------------------------------QHistoryState *s1h = new QHistoryState(s1);QState *s3 = new QState();s3->assignProperty(label, "text", "In s3");QMessageBox *mbox = new QMessageBox;mbox->addButton(QMessageBox::Ok);mbox->setText("Interrupted!");mbox->setIcon(QMessageBox::Information);QObject::connect(s3, SIGNAL(entered()), mbox, SLOT(exec()));// 由中断状态转移到历史状态s3->addTransition(s1h);// 当中断按钮被点击时,状态转移到s3 中断状态s1->addTransition(interruptButton, SIGNAL(clicked()), s3);//------------------------------------------------------------machine.addState(s1); // 运行状态machine.addState(s2); // 结束状态machine.addState(s3); // 中止状态// 设置状态机初始状态s1machine.setInitialState(s1);// 启动状态机machine.start();w.show();return a.exec();
}

【运行效果】:
在这里插入图片描述

四、从源码角度剖析几个关键的函数

*!\fn template <typename PointerToMemberFunction> QState::addTransition(const QObject *sender, PointerToMemberFunction signal, QAbstractState *target);\since 5.5\overloadAdds a transition associated with the given \a signal of the given \a senderobject, and returns the new QSignalTransition object. The transition hasthis state as the source, and the given \a target as the target state.
*//*!Adds a transition associated with the given \a signal of the given \a senderobject, and returns the new QSignalTransition object. The transition hasthis state as the source, and the given \a target as the target state.
*/
QSignalTransition *QState::addTransition(const QObject *sender, const char *signal,QAbstractState *target)
{if (!sender) {qWarning("QState::addTransition: sender cannot be null");return 0;}if (!signal) {qWarning("QState::addTransition: signal cannot be null");return 0;}if (!target) {qWarning("QState::addTransition: cannot add transition to null state");return 0;}int offset = (*signal == '0'+QSIGNAL_CODE) ? 1 : 0;const QMetaObject *meta = sender->metaObject();if (meta->indexOfSignal(signal+offset) == -1) {if (meta->indexOfSignal(QMetaObject::normalizedSignature(signal+offset)) == -1) {qWarning("QState::addTransition: no such signal %s::%s",meta->className(), signal+offset);return 0;}}QSignalTransition *trans = new QSignalTransition(sender, signal);trans->setTargetState(target);addTransition(trans);return trans;
}
/*!Adds the given \a state to this state machine. The state becomes a top-levelstate and the state machine takes ownership of the state.If the state is already in a different machine, it will first be removedfrom its old machine, and then added to this machine.\sa removeState(u), setInitialState()
*/
void QStateMachine::addState(QAbstractState *state)
{if (!state) {qWarning("QStateMachine::addState: cannot add null state");return;}if (QAbstractStatePrivate::get(state)->machine() == this) {qWarning("QStateMachine::addState: state has already been added to this machine");return;}state->setParent(this);
}

五、总结

本篇尚有状态机的部分使用示例没有展示,特别是并行状态机及其他更复杂的状态转换等。不过楼太高,看着会累。话说笔者也已经累了,遂欲起下篇。有兴趣者,请君移步。


参考文档:
1、https://blog.csdn.net/qq_35629971/article/details/125988152
2、https://www.wisdom.weizmann.ac.il/~dharel/SCANNED.PAPERS/Statecharts.pdf

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/410575.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

12AOP面向切面编程/GoF之代理模式

先看一个例子&#xff1a; 声明一个接口&#xff1a; // - * / 运算的标准接口! public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j); }实现该接口&#xff1a; package com.sunsplanter.prox…

Controller层自定义注解拦截request请求校验

一、背景 笔者工作中遇到一个需求&#xff0c;需要开发一个注解&#xff0c;放在controller层的类或者方法上&#xff0c;用以校验请求参数中(不管是url还是body体内&#xff0c;都要检查&#xff0c;有token参数&#xff0c;且符合校验规则就放行)是否传了一个token的参数&am…

Linux中的yum源仓库和NFS文件共享服务

一.yum简介 1.1 yum简介 yum&#xff0c;全称“Yellow dog Updater, Modified”&#xff0c;是一个专门为了解决包的依赖关系而存在的软件包管理器。类似于windows系统的中电脑软件关键&#xff0c;可以一键下载&#xff0c;一键安装和卸载。yum 是改进型的 RPM 软件管理器&am…

普通人做VR全景创业,优势表现在哪里?

伴随着5G时代的到来&#xff0c;快节奏的生活带动了“宅经济”、“云生活”的发展&#xff0c;消费者也越来越依赖智能化设备给生活带来的便利。因此VR全景将成为未来消费升级的一种新媒介&#xff0c;VR全景能够将企业环境以及产品、服务等信息更直观、透明化地展示给用户&…

01章【JAVA开发入门】

计算机基本概念 计算机组成原理 计算机组装 计算机&#xff1a;电子计算机&#xff0c;俗称电脑。是一种能够按照程序运行&#xff0c;自动、高速处理海量数据的现代化智能电子设备。由硬件和软件所组成&#xff0c;没有安装任何软件的计算机称为裸机。常见的形式有台式计算机、…

【GCC】6 接收端实现:周期构造RTCP反馈包

基于m98代码。GCC涉及的代码,可能位于:webrtc/modules/remote_bitrate_estimator webrtc/modules/congestion_controller webrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.cc webrtc 之 RemoteEstimatorProxy 对 remote_bitrate_estimator 的 RemoteEstimato…

SECS通讯资料大全 配方处理 recipe上传下载 S7Fx和S7F3、S7F5

在SECS/GEM里面&#xff0c;recipe配方称为Process Recipe Management Process Recipe Management — 设备处理规范&#xff08;如配方&#xff09;必须通过设备和主机系统之间的交互进行管理。 PPID也就是Process Recipe ID的意思 配方是怎么交互和处理呢 COMMENTS HOST EQ…

.NET 8.0 发布到 IIS

如何在IIS&#xff08;Internet信息服务&#xff09;上发布ASP.NET Core 8&#xff1f; 在本文中&#xff0c;我假设您的 Windows Server IIS 上已经有一个应用程序池。 按照步骤了解在 IIS 环境下发布 ASP.NET Core 8 应用程序的技巧。 您需要设置代码以支持 IIS 并将项目配…

docker screen 常用基础命令

1.docker基础命令 1.1开启docker systemctl start docker #开启docker service docker restart #重启docker systemctl stop docker #关闭docker 1.2查看命令 docker images #查看docker镜像docker ps #查看正在运行的镜像或者容器docker ps -a #查看所有容器1.3运…

线程的使用

线程的创建方式 1、实现Runnable Runnable规定的方法是run()&#xff0c;无返回值&#xff0c;无法抛出异常 实现Callable 2、Callable规定的方法是call()&#xff0c;任务执行后有返回值&#xff0c;可以抛出异常 3、继承Thread类创建多线程 继承java.lang.Thread类&#xff0…

计算机毕业设计 基于Java的美食信息推荐系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

docker使用nginx部署vue刷新页面404

docker使用nginx部署vue刷新页面404 从docker内部复制出来的配置文件是这样的&#xff0c;但是刷新页面之后就显示404&#xff0c;关键是我两个前端项目都是用的这一个配置文件&#xff0c;但是只有一个项目出现刷新浏览器显示404的问题&#xff0c;这给我搞懵了&#xff01;&…