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

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

1、利用并行态避免态的组合爆炸

假设您想在单个状态机中对汽车的一组互斥属性进行建模。假设我们感兴趣的属性是干净与肮脏,以及移动与不移动。需要四个相互排斥的状态和八个转换才能表示所有可能的组合并在它们之间自由移动。
在这里插入图片描述
如果我们增加第三个属性(比如红色与蓝色),状态的总数将翻一番,达到8个;如果我们增加第四个属性(比如,封闭与可转换),状态的总数将再次翻倍,达到16。

使用并行状态,状态和转换的总数随着我们添加更多属性而线性增长,而不是指数增长。此外,状态可以添加到并行状态或从并行状态中删除,而不会影响其任何兄弟状态。

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


#include <QApplication>
#include <QWidget>#include <QState>
#include <QStateMachine>
#include <QPushButton>
#include <QLabel>int main(int argc, char *argv[])
{QApplication a(argc, argv);QWidget w;QPushButton* controlButton_0 = new QPushButton(QStringLiteral("雨刷"),&w);QPushButton* controlButton_1 = new QPushButton(QStringLiteral("刹车"),&w);QLabel* label_0 = new QLabel(&w);QLabel* label_1 = new QLabel(&w);controlButton_0->setGeometry(0,0,200,60);controlButton_1->setGeometry(0,70,200,60);label_0->setGeometry(200,0,200,60);label_1->setGeometry(200,70,200,60);QStateMachine machine;QState* s1  = new QState(QState::ParallelStates);{QState* s11 = new QState(s1);{QState* s11_clean = new QState(s11);QState* s11_dirty = new QState(s11);s11_clean->assignProperty(label_0,"text","clean");s11_dirty->assignProperty(label_0,"text","dirty");s11_clean->addTransition(controlButton_0,SIGNAL(clicked(bool)),s11_dirty);s11_dirty->addTransition(controlButton_0,SIGNAL(clicked(bool)),s11_clean);s11->setInitialState(s11_clean);}QState* s12 = new QState(s1);{QState* s12_notMoving = new QState(s12);QState* s12_moving = new QState(s12);s12_notMoving->assignProperty(label_1,"text","not moving");s12_moving->assignProperty(label_1,"text","moving");s12_notMoving->addTransition(controlButton_1,SIGNAL(clicked(bool)),s12_moving);s12_moving->addTransition(controlButton_1,SIGNAL(clicked(bool)),s12_notMoving);s12->setInitialState(s12_notMoving);}}machine.addState(s1);machine.setInitialState(s1);machine.start();w.show();return a.exec();
}

【运行效果】:
假设汽车已经启动。开/关雨刷和是否踩住刹车是两个并行的,互不干扰的状态。
在这里插入图片描述

2、状态结束状态检测

在示例1的基础上,我们对汽车的 【启动->运行->结束】这一完整的过程进行简单的状态模拟。
在实际生活中,我们踩住刹车同时按住【一键启动】按钮,汽车点火,进入s1启动状态。当汽车熄火,进入s2状态。当汽车驻车和锁闭车门后,进入s3状态。生活中的car 启停和运行过程中的操作状态切换模型,远比我们下图中所绘制的要复杂的多。本例对模型进行适当的简化。

在启动状态下,我们可以对汽车进行一些操作,有些操作状态时串行的,也有一些操作状态是并行的。 并行的状态譬如我们在示例1或者如下状态表所示的 开关雨刷与控制汽车的行驶和停止;串行的状态,譬如我们汽车点火和熄火两个状态,他不能同时存在,类似这样的状态我们认为是串行的。不过,串行的状态还可以分为循环状态和 可中止状态。 譬如我们打开车门,在主驾驶座位上可以循环重复【打火启动】->【停车熄火】这两个状态的切换;但是如果我们已经下车,在【熄火】状态下,我们可以将车门重【未锁车】状态切换到【锁车】终止状态。

当进入到终止状态(QFinalState)后,会发送 finished信号
在这里插入图片描述

【codes】:


#include <QApplication>
#include <QWidget>#include <QState>
#include <QStateMachine>
#include <QFinalState>#include <QPushButton>
#include <QLabel>int main(int argc, char *argv[])
{QApplication a(argc, argv);QWidget w;QPushButton* fire_button = new QPushButton(QStringLiteral("点火/熄火"),&w);QPushButton* controlButton_0 = new QPushButton(QStringLiteral("雨刷"),&w);QPushButton* controlButton_1 = new QPushButton(QStringLiteral("刹车"),&w);QPushButton* lock_button = new QPushButton(QStringLiteral("锁车"),&w);QLabel* label_0 = new QLabel(&w);QLabel* label_1 = new QLabel(&w);fire_button->setGeometry(0,300,200,60);lock_button->setGeometry(0,400,200,60);controlButton_0->setGeometry(0,0,200,60);controlButton_1->setGeometry(0,70,200,60);label_0->setGeometry(200,0,200,60);label_1->setGeometry(200,70,200,60);QStateMachine machine;QState* car = new QState();QState* s1  = new QState(QState::ParallelStates,car);{QState* s11 = new QState(s1);{QState* s11_clean = new QState(s11);QState* s11_dirty = new QState(s11);s11_clean->assignProperty(label_0,"text","clean");s11_dirty->assignProperty(label_0,"text","dirty");s11_clean->addTransition(controlButton_0,SIGNAL(clicked(bool)),s11_dirty);s11_dirty->addTransition(controlButton_0,SIGNAL(clicked(bool)),s11_clean);s11->setInitialState(s11_clean);}QState* s12 = new QState(s1);{QState* s12_notMoving = new QState(s12);QState* s12_moving = new QState(s12);s12_notMoving->assignProperty(label_1,"text","not moving");s12_moving->assignProperty(label_1,"text","moving");s12_notMoving->addTransition(controlButton_1,SIGNAL(clicked(bool)),s12_moving);s12_moving->addTransition(controlButton_1,SIGNAL(clicked(bool)),s12_notMoving);s12->setInitialState(s12_notMoving);}}QState* s2 = new QState(car);{QState* s21 = new QState(s2);QFinalState* finalState = new QFinalState(s2);s21->addTransition(lock_button,SIGNAL(clicked(bool)),finalState);s2->setInitialState(s21);}s1->addTransition(fire_button,SIGNAL(clicked(bool)),s2);s2->assignProperty(label_0,"text",QStringLiteral("已熄火"));s2->assignProperty(label_1,"text",u8"已熄火");QState* s3 = new QState(car);s2->addTransition(s2, SIGNAL(finished()), s3);s3->assignProperty(label_0,"text",u8"已熄火锁车");s3->assignProperty(label_1,"text",u8"已熄火锁车");car->setInitialState(s1);machine.addState(car);machine.setInitialState(car);machine.start();w.show();return a.exec();
}

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

解释一下代码演示的流程:
程序启动,状态机启动,进入到默认的car.s1状态(点火状态),s1是一个并行的子状态机,包括雨刷和刹车这两个对象的状态控制,所以如图演示的,我们点击雨刷和刹车按钮,可以自由切换两个对象的状态,互补干扰;
当我们点击【点火/熄火】按钮,car的状态切换到car.s2(熄火状态),s2是一个串行的状态机,默认状态是[熄火/未锁车],当我们点击【锁车】按钮时,car的状态切换到car.s2.finalState s2状态结束,发送一个finished的信号,car切换到s3状态。

至此,一个相对复杂一点儿的状态机模型我们演示完成。当然实际的应用场景往往比这个要复杂的更多。但是我们只要保证一下几个步骤/原则,无论多复杂的状态转换模型,都可以轻而易举的完成。

* 1、状态模块划分(先父后子)
* 2、区分并行or串行
* 3、状态是否要中断
* 4、是否有结束状态
* 5、父状态需添加默认子状态
* 6、状态机需设置默认顶层状态

3、无目标状态的状态转移

有时候,我们会有这样的场景,当前状态(假设是s1)在接收到某个信号或者事件之后,转移动作被触发。我们可以捕获转移 对象(QAbstractTransition或者其子对象)发出的 triggered 信号,并在槽函数中做一些业务处理。当结束业务处理逻辑时,有趣的是,当前状态仍是s1。这样,一个动作就可以反复的react,就很适合重复性的状态转换了。

【codes】:

#include <QApplication>
#include <QState>
#include <QStateMachine>
#include <QSignalTransition>#include <QSignalMapper>
#include <QPushButton>
#include <QMessageBox>int main(int argc, char *argv[])
{QApplication a(argc, argv);QWidget w;QStateMachine machine;QState* s1 = new QState();machine.addState(s1);QPushButton* button = new QPushButton(QStringLiteral("触发"),&w);button->setGeometry(150,150,100,60);QSignalTransition* trans = new QSignalTransition(button,SIGNAL(clicked(bool)));s1->addTransition(trans);  // 无目标状态转移QMessageBox box;box.setText(QStringLiteral("The button was clicked, carry on."));QObject::connect(trans,SIGNAL(triggered()),&box,SLOT(exec()));machine.setInitialState(s1);machine.start();w.show();return a.exec();
}

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

4、事件、转换和防护

QStateMachine运行自己的事件循环。对于信号转换(QSignalTransition对象),QStateMachine在截获相应信号时会自动向自身发布QStateMachine::SignalEvent;类似地,对于QObject事件转换(QEventTransition对象),会发布一个QStateMachine::WrappedEvent
您可以使用QStateMachine::postEvent( )将自己的事件发布到状态机。
将自定义事件发布到状态机时,通常还会有一个或多个自定义转换,这些转换可以由该类型的事件触发。要创建这样的转换,您可以将QAbstractTransition子类化并重新实现QAbstract transition::eventTest( ),在其中检查事件是否与您的事件类型(以及可选的其他条件,例如事件对象的属性)匹配。

在下面的例子里,我们定义了自己的自定义事件类型StringEvent,用于将字符串发布到状态机:
在这里插入图片描述
【codes】:


```cpp
#include <QApplication>
#include <QState>
#include <QStateMachine>
#include <QSignalTransition>
#include <QFinalState>
#include <QDebug>#include <QSignalMapper>
#include <QPushButton>
#include <QMessageBox>
#include <QLineEdit>
#include <QLabel>
#include <QHBoxLayout>
#include <QVBoxLayout>struct StringEvent : public QEvent
{StringEvent(const QString& val): QEvent(QEvent::Type(QEvent::User+1)),value(val){}QString value;
};struct StringTransition : public QAbstractTransition
{Q_OBJECT
public:StringTransition(const QString& value) : m_value(value){}protected:bool eventTest(QEvent *e){if (e->type() != QEvent::Type(QEvent::User+1)) // StringEventreturn false;StringEvent *se = static_cast<StringEvent*>(e);return (m_value == se->value);}void onTransition(QEvent* e) override { Q_UNUSED(e);}
private:QString m_value;
};int main(int argc, char *argv[])
{QApplication a(argc, argv);QWidget w;QLineEdit* s1_ldt = new QLineEdit;QLineEdit* s2_ldt = new QLineEdit;QLabel* s_lab = new QLabel;QHBoxLayout* s1_hlayout = new QHBoxLayout;s1_hlayout->addWidget(new QLabel("s1:"));s1_hlayout->addWidget(s1_ldt);QHBoxLayout* s2_hlayout = new QHBoxLayout;s2_hlayout->addWidget(new QLabel("s2:"));s2_hlayout->addWidget(s2_ldt);QVBoxLayout* vlayout = new QVBoxLayout;vlayout->addLayout(s1_hlayout);vlayout->addLayout(s2_hlayout);vlayout->addWidget(s_lab);w.setLayout(vlayout);QStateMachine machine;QState* s1 = new QState();QState* s2 = new QState();QFinalState* done = new QFinalState();s1->assignProperty(s_lab,"text","state in s1");s2->assignProperty(s_lab,"text","state in s2");QObject::connect(s1_ldt,&QLineEdit::textChanged,[&](){machine.postEvent( new StringEvent(s1_ldt->text().trimmed()));});QObject::connect(s2_ldt,&QLineEdit::textChanged,[&](){machine.postEvent(new StringEvent(s2_ldt->text().trimmed()));});QObject::connect(done,&QFinalState::entered,[&](){s_lab->setText("state in final state.");qDebug() << "state in final state.";});QObject::connect(done,&QFinalState::exited,[&](){s_lab->setText("final state has exited.");});// s1->s2->doneStringTransition* t1 = new StringTransition("hello");t1->setTargetState(s2);s1->addTransition(t1);StringTransition* t2 =new StringTransition("world");t2->setTargetState(done);s2->addTransition(t2);machine.addState(s1);machine.addState(s2);machine.addState(done);machine.setInitialState(s1);machine.start();w.show();return a.exec();
}#include "main.moc"

【运行效果】:

在这里插入图片描述


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

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

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

相关文章

汇川PLC与工业远程透传模块远程上下载程序

准备工作 一台可联网操作的电脑一台单网口的远程透传网关及博达远程透传配置工具网线一条&#xff0c;用于实现网络连接和连接PLC一台汇川 H5U-1614MTD-A8 PLC及其编程软件一张4G卡或WIFI天线实现通讯(使用4G联网则插入4G SIM卡&#xff0c;WIFI联网则将WIFI天线插入USB口&…

数据守护盾牌:敏感数据扫描与脱敏,让安全合规无忧

前言 在信息时代&#xff0c;数据已经成为企业和组织的核心资产&#xff0c;其价值与日俱增。然而&#xff0c;随着数据使用的普及和复杂度的提升&#xff0c;数据安全与合规问题也变得越来越突出。敏感数据的保护显得尤为重要&#xff0c;因为这些数据一旦泄露或被不当使用&a…

AI教我学编程之C#类的基本概念(1)

前言 在AI教我学编程之C#类型 中&#xff0c;我们学习了C#类型的的基础知识&#xff0c;而类正是类型的一种. 目录 区分类和类型 什么是类&#xff1f; 对话AI 追问 实操 追踪属性的使用 AI登场 逐步推进 提出疑问 药不能停 终于实现 探索事件的使用 异步/交互操作 耗时操…

【前后端的那些事】评论功能实现

文章目录 聊天模块1. 数据库表2. 后端初始化2.1 controller2.2 service2.3 dao2.4 mapper 3. 前端初始化3.1 路由创建3.2 目录创建3.3 tailwindCSS安装 4. tailwindUI5. 前端代码编写 前言&#xff1a;最近写项目&#xff0c;发现了一些很有意思的功能&#xff0c;想写文章&…

前端——框架——Vue

提示&#xff1a; 本文只是从宏观角度简要地梳理一遍vue3&#xff0c;不至于说学得乱七八糟、一头雾水、不知南北&#xff0c;如果要上手写代码、撸细节&#xff0c;可以根据文中的关键词去查找资料 简问简答&#xff1a; vue.js是指vue3还是vue2&#xff1f; Vue.js通常指的是…

回馈科教,非凸科技助力第48届ICPC亚洲区决赛

1月12日-14日&#xff0c;“华为杯”第48届国际大学生程序设计竞赛&#xff08;ICPC&#xff09;亚洲区决赛在上海大学成功举办。非凸科技作为此次赛事的支持方之一&#xff0c;希望携手各方共同推动计算机科学和技术的发展。 这是一场智慧的巅峰对决&#xff0c;320支优秀队伍…

K8s-架构

一、K8s节点划分 K8s集群包含Master(控制节点)和Node(工作节点)&#xff0c;应用部署在Node节点上。 集群架构图&#xff1a; 二、Master节点 Master节点分成四个组件&#xff1a;scheduler、ApiServer、Controller Manager、ETCD。类似三层结构&#xff0c;controller&#…

20240117-【UNITY 学习】增加墙跑功能和跳墙功能

替换脚本PlayerCam_01.cs using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening;public class PlayerCam_02 : MonoBehaviour {// 视觉灵敏度参数public float sensX 400;public float sensY 400;// 视角垂直旋转角度限制publ…

redis安装-Linux为例

可以下载一个Shell或者MobaXterm工具&#xff0c;便于操作 在redis官网下载压缩包 开始安装 安装依赖 yum install -y gcc tcl切换目录 切换目录后直接把redis安装包拖到/user/local/src/下 cd /user/local/src/解压然后安装 #解压 tar -zxvf redis-7.2.4.tar.gz #安装 …

Kafka-消费者-KafkaConsumer分析-ConsumerNetworkClient

前面介绍过NetworkClient的实现&#xff0c;它依赖于KSelector、InFlightRequests、Metadata等组件&#xff0c;负责管理客户端与Kafka集群中各个Node节点之间的连接&#xff0c;通过KSelector法实现了发送请求的功能&#xff0c;并通过一系列handle*方法处理请求响应、超时请求…

阿里云云原生助力安永创新驱动力实践探索

云原生正在成为新质生产力变革的核心要素和企业创新的数字基础设施。2023 年 12 月 1 日&#xff0c;由中国信通院举办的“2023 云原生产业大会”在北京召开。在大会“阿里云云原生”专场&#xff0c;安永科技咨询合伙人王祺分享了对云原生市场的总览及趋势洞见&#xff0c;及安…

React的合成事件

合成事件&#xff1a;通过事件委托&#xff0c;利用事件传播机制&#xff0c;当事件传播到document时&#xff0c;再进行分发到对应的组件&#xff0c;从而触发对应所绑定的事件&#xff0c;然后事件开始在组件树DOM中走捕获冒泡流程。 原生事件 —— > React事件 —— >…