深入理解Qt状态机的应用(二)

前文《深入理解Qt状态机的应用(一)》介绍了状态机的理论知识以及简单的状态机示例。
在实际应用场景中,状态机往往会比较复杂;本文将详细介绍分组状态、历史状态、并行状态以及其他技术。

通过分组状态共享转换

还是以交通信号灯系统为例,上一篇文章中已经实现了简单的信号灯状态机系统,但只是正常情况下的状态转换。
在实际应用场景中,信号灯除了红黄绿相互跳转外,还会存在一种情况是一直跳闪烁黄灯。我们要如何实现呢?

用按钮事件来模拟触发红黄绿三种状态转换为一直闪烁黄灯状态

// 添加闪烁黄灯状态
QState *flashYellowState = new QState(m_stateMachine);
// 闪烁黄灯状态下的各个对象状态
flashYellowState->assignProperty(ui->redBtn, "visible", false);
flashYellowState->assignProperty(ui->yellowBtn, "visible", true);
flashYellowState->assignProperty(ui->greenBtn, "visible", false);
// 添加其他状态在按钮事件触发时跳转到闪烁黄灯状态
redState->addTransition(ui->button, &QPushButton::clicked, flashYellowState);
yellowState->addTransition(ui->button, &QPushButton::clicked, flashYellowState);
greenState->addTransition(ui->button, &QPushButton::clicked, flashYellowState);

上面代码中因为红黄绿三个状态都在按钮事件的触发下转换为闪烁黄灯状态,这种重复的代码就会比较多。
我们考虑这个示例中一共有四个状态,其中红黄绿三个状态属于一组正常逻辑状态,而闪烁黄灯属于异常逻辑状态,信号灯要么是正常逻辑跳转,要么就是异常逻辑跳转。所以,这里可以用正常逻辑和异常逻辑两个状态作为顶层状态,而红黄绿作为正常逻辑状态的子状态,闪烁黄灯作为异常逻辑状态的子状态,代码如下:

QState *normalState = new QState(m_stateMachine);     // 正常的状态组
QState *redState = new QState(normalState);           // 红灯状态
QState *yellowState = new QState(normalState);        // 黄灯状态
QState *greenState = new QState(normalState);         // 绿灯状态
normalState->setInitialState(redState);QState *abnormalState = new QState(m_stateMachine);   // 异常的状态组
QState *flashYellowState = new QState(abnormalState); // 闪烁黄灯状态
abnormalState->setInitialState(flashYellowState);
...初始化状态机
// 设置状态机的初始状态
m_stateMachine->setInitialState(normalState);

既然红黄绿状态作为了正常逻辑状态的子状态,那么这三个状态跳转到异常逻辑状态的转换代码可以改成:

normalState->addTransition(ui->button, &QPushButton::clicked, abnormalState);
// 解决重复代码问题:下面的代码就可以不用了
// redState->addTransition(ui->button, &QPushButton::clicked, flashYellowState);
// yellowState->addTransition(ui->button, &QPushButton::clicked, flashYellowState);
// greenState->addTransition(ui->button, &QPushButton::clicked, flashYellowState);

所以,通过状态分组,然后只需通过对父状态添加转换就能实现所有子状态的转换逻辑。
这就是分组状态共享转换。

注意
一个状态只能成为一个父状态的子状态,不能同时存在多个父状态。

用历史状态保存和恢复当前状态

上面将信号灯分成了正常逻辑状态和异常逻辑状态两个顶层状态,将红黄绿作为正常逻辑状态的子状态,在故障按钮触发的情况下,正常逻辑状态转换为异常逻辑状态。
那现在假设在从正常逻辑转换为异常逻辑后,又要转换为正常逻辑状态时要求恢复成故障之前的正常逻辑状态。比如在绿灯的时候,发生故障,转换为异常逻辑状态,然后在修复故障后恢复回绿灯状态,而不是又从红灯开始转换。

QHistoryState

QHistoryState是一个伪状态,用来记录状态机退出某父状态时父状态所处的子状态。

用法

QHistoryState的用法主要包含以下流程:

  1. 创建历史状态并绑定复合状态(父状态)

QHistoryState *normalHisState = new QHistoryState(normalState); // 历史状态

  1. 添加转换(从同一个状态机的其他复合状态转换为历史状态)

abnormalState->addTransition(ui->button2, &QPushButton::clicked, normalHisState);

示例代码

void Widget::initStateMachine()
{创建状态QState *normalState = new QState(m_stateMachine); // 正常的状态组QState *redState = new QState(normalState);       // 红灯状态QState *yellowState = new QState(normalState);    // 黄灯状态QState *greenState = new QState(normalState);     // 绿灯状态normalState->setInitialState(redState);QHistoryState *normalHisState = new QHistoryState(normalState); // 历史状态QState *abnormalState = new QState(m_stateMachine);   // 异常的状态组QState *flashYellowState = new QState(abnormalState); // 闪烁黄灯状态abnormalState->setInitialState(flashYellowState);初始化状态的属性// 红灯状态下,各个按钮显示的状态redState->assignProperty(ui->redBtn, "visible", true);redState->assignProperty(ui->yellowBtn, "visible", false);redState->assignProperty(ui->greenBtn, "visible", false);// 黄灯状态下,各个按钮显示的状态yellowState->assignProperty(ui->redBtn, "visible", false);yellowState->assignProperty(ui->yellowBtn, "visible", true);yellowState->assignProperty(ui->greenBtn, "visible", false);// 绿灯状态下,各个按钮显示的状态greenState->assignProperty(ui->redBtn, "visible", false);greenState->assignProperty(ui->yellowBtn, "visible", false);greenState->assignProperty(ui->greenBtn, "visible", true);// 闪烁黄灯状态下,各个按钮显示的状态flashYellowState->assignProperty(ui->redBtn, "visible", false);flashYellowState->assignProperty(ui->yellowBtn, "visible", true);flashYellowState->assignProperty(ui->greenBtn, "visible", false);初始化状态转换过程// 红灯在红灯计时器超时后转换为绿灯redState->addTransition(&m_redTimer, &QTimer::timeout, greenState);// 在进入绿灯状态后,要启动绿灯定时器connect(greenState, &QState::entered, [&]() { m_greenTimer.start(); });// 绿灯在绿灯计时器超时后转换为黄灯greenState->addTransition(&m_greenTimer, &QTimer::timeout, yellowState);// 在进入黄灯状态后,要启动黄灯定时器connect(yellowState, &QState::entered, [&]() { m_yellowTimer.start(); });// 黄灯在黄灯计时器超时后转化为红灯yellowState->addTransition(&m_yellowTimer, &QTimer::timeout, redState);// 在进入红灯状态后,要启动红灯定时器connect(redState, &QState::entered, [&]() { m_redTimer.start(); });normalState->addTransition(ui->button, &QPushButton::clicked, abnormalState);abnormalState->addTransition(ui->button2, &QPushButton::clicked, normalHisState);初始化状态机// 设置状态机的初始状态m_stateMachine->setInitialState(normalState);// 开启状态机m_stateMachine->start();
}

效果

Video_2024-06-19_110551.gif

并行组合复合状态

本节内容将通过一个手动建模画布的示例来介绍如何通过并行组合复合状态来解决复杂的状态逻辑问题。

并行关系指的是同级状态之间相互独立,互不影响。

示例需求说明

手动建模画布的功能包含:

  1. 画操作
    1. 画直线
    2. 画弧线
    3. 画圆
    4. 画矩形
  2. 选择模式
    1. 选择线模式
    2. 选择面模式
  3. 显示数据
    1. 显示背景网格
    2. 显示鼠标坐标

整理状态逻辑

在处于画操作状态时,用户切换选择模式会结束画操作,所以画操作和选择模式属于互斥关系。
而显示数据的切换是不影响前面两个功能的,所以显示数据和前面的操作属于并行关系。
这些状态的逻辑关系如下:

  1. 互斥关系1:
    1. 画直线
    2. 画弧线
    3. 画圆
    4. 画矩形
    5. 选择线模式
    6. 选择面模式
  2. 互斥关系2:
    1. 显示背景网格
    2. 不显示背景网格
  3. 互斥关系3:
    1. 显示鼠标坐标
    2. 不显示鼠标坐标
  4. 并行关系
    1. 显示背景网格
    2. 显示鼠标坐标
    3. 画操作&选择模式

整理清楚整体的状态逻辑关系非常重要。

实现

既然画操作和选择模式属于互斥关系,那么在它们的上一层用一个互斥状态对象(operatorState)来包装;显示数据和其他属于并行关系,所以可以在外层用并行状态(rootState)来包装。

注意
状态机(QStateMachine)的ChildMode设置为并行模式(ParallelStates)会导致状态机无效。
它只能是互斥模式(ExclusiveStates)。

void Widget::initStateMachine()
{// 根状态为并行状态,包装操作状态和显示数据状态属于并行关系QState *rootState = new QState(QState::ParallelStates, m_stateMachine);// 操作状态(包含画操作、选择模式操作){QState *operatorState = new QState(rootState);QState *paintOperState = new QState(operatorState);QState *paintLineState = new QState(paintOperState);QState *paintArcState = new QState(paintOperState);QState *paintRectState = new QState(paintOperState);QState *paintCircleState = new QState(paintOperState);paintOperState->setInitialState(paintLineState);operatorState->setInitialState(paintOperState);QState *modelOperState = new QState(operatorState);QState *selLineState = new QState(modelOperState);QState *selFaceState = new QState(modelOperState);modelOperState->setInitialState(selLineState);paintLineState->assignProperty(ui->paintLabel, "text", GETLABELTEXT("画直线"));paintArcState->assignProperty(ui->paintLabel, "text", GETLABELTEXT("画弧线"));paintRectState->assignProperty(ui->paintLabel, "text", GETLABELTEXT("画矩形"));paintCircleState->assignProperty(ui->paintLabel, "text", GETLABELTEXT("画圆"));selLineState->assignProperty(ui->modelLabel, "text", GETLABELTEXT("选择线模式"));selFaceState->assignProperty(ui->modelLabel, "text", GETLABELTEXT("选择面模式"));struct bindTransitionObj{QPushButton *m_sender;QState      *m_state;};QVector<bindTransitionObj> bindTransitions;bindTransitions.append({ui->paintLineBtn, paintLineState});bindTransitions.append({ui->paintArcBtn, paintArcState});bindTransitions.append({ui->paintCircleBtn, paintCircleState});bindTransitions.append({ui->paintRectBtn, paintRectState});bindTransitions.append({ui->selectLineBtn, selLineState});bindTransitions.append({ui->selectFaceBtn, selFaceState});for (auto fromObj : bindTransitions) {for (auto toObj : bindTransitions) {if (fromObj.m_state != toObj.m_state) {addTransition(fromObj.m_state, toObj.m_sender, toObj.m_state);}}}}{// 显示状态,属于并行状态QState *showState = new QState(QState::ParallelStates, rootState);// 初始化显示背景网格状态QState *gridState = new QState(showState);QState *showGridState = new QState(gridState);QState *notShowGridState = new QState(gridState);// 初始化显示鼠标坐标状态QState *positionState = new QState(showState);QState *showPositionState = new QState(positionState);QState *notShowPositionState = new QState(positionState);gridState->setInitialState(notShowGridState);positionState->setInitialState(notShowPositionState);showGridState->assignProperty(ui->gridLabel, "text", GETLABELTEXT("显示背景网格"));notShowGridState->assignProperty(ui->gridLabel, "text", GETLABELTEXT("不显示背景网格"));showPositionState->assignProperty(ui->positionLabel, "text", GETLABELTEXT("显示鼠标坐标"));notShowPositionState->assignProperty(ui->positionLabel, "text", GETLABELTEXT("不显示鼠标坐标"));// 显示背景网格内部的状态转换addTransition(showGridState, ui->showGridBtn, notShowGridState);addTransition(notShowGridState, ui->showGridBtn, showGridState);// 显示鼠标坐标内部的状态转换addTransition(showPositionState, ui->showPositionBtn, notShowPositionState);addTransition(notShowPositionState, ui->showPositionBtn, showPositionState);}初始化状态机m_stateMachine->setInitialState(rootState);m_stateMachine->start();
}void Widget::addTransition(QState *fromState, QPushButton *btn, QState *toState)
{fromState->addTransition(btn, &QPushButton::clicked, toState);
}

效果

image.png

源码链接

LeoLei8060@github
LeoLei8060@gitee

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

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

相关文章

基于若依的ruoyi-nbcio流程管理系统增加所有任务功能(一)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; h…

如何理解光学中的群速度和相速度。

我不太明白为什么书上要区分相速度和群速度&#xff0c;不管这个&#xff0c;我想看看这两个速度在真实周期函数上的影响是如何的。 首先计算&#xff0c;直接计算三角函数我不会&#xff0c;利用复数做&#xff0c;可以取的实部。其中&#xff0c;。。 这个公式说明了什么呢…

React@16.x(34)动画(中)

目录 3&#xff0c;SwitchTransition3.1&#xff0c;原理3.1.2&#xff0c;key3.1.2&#xff0c;mode 3.2&#xff0c;举例3.3&#xff0c;结合 animate.css 4&#xff0c;TransitionGroup4.1&#xff0c;其他属性4.1.2&#xff0c;appear4.1.2&#xff0c;component4.1.3&…

Qt Quick 教程(一)

文章目录 1.Qt Quick2.QML3.Day01 案例main.qml退出按钮&#xff0c;基于上面代码添加 4.使用Qt Design StudioQt Design Studio简介Qt Design Studio工具使用版本信息 1.Qt Quick Qt Quick 是一种现代的用户界面技术&#xff0c;将声明性用户界面设计和命令性编程逻辑分开。 …

前后端完整案例-简单模仿点点开黑抽奖

数据库 后台 源码&#xff1a;https://gitee.com/qfp17393120407/game 前台 源码&#xff1a; https://gitee.com/qfp17393120407/game-weeb vue项目打包 注意&#xff1a;打包时将IP改为自己公网IP npm run build公网页面 地址&#xff1a;点点模拟抽奖 进入页面抽奖…

不同表格式下的小文件治理方式(开源RC file/ORC/Text非事务表、事务表、Holodesk表格式..)

友情链接&#xff1a; 小文件治理系列之为什么会出现小文件问题&#xff0c;小文件过多问题的危害以及不同阶段下的小文件治理最佳解决手段 小文件过多的解决方法&#xff08;不同阶段下的治理手段&#xff0c;SQL端、存储端以及计算端&#xff09; 概览 在前两篇博文中&am…

自学鸿蒙HarmonyOS的ArkTS语言<一>基本语法

一、一个ArkTs的目录结构 二、一个页面的结构 A、装饰器 Entry 装饰器 : 标记组件为入口组件&#xff0c;一个页面由多个自定义组件组成&#xff0c;但是只能有一个组件被标记 Component : 自定义组件, 仅能装饰struct关键字声明的数据结构 State&#xff1a;组件中的状态变量…

python全栈开发《10.数据类型之初识列表类型》

1.什么是列表 其实在生活中&#xff0c;有很多种排队的现象。比如看电影要排队买票&#xff0c;上地铁的时候要排队安检。在生活中&#xff0c;排队的是人&#xff0c;为了统一做一件事&#xff0c;而排成队伍&#xff0c;逐个的去等待执行这个任务&#xff0c;每个人都是执行这…

平衡查找树(数据结构篇)

数据结构之平衡查找树 平衡查找树(AVL树) 概念&#xff1a; 为了防止因为插入删除而导致的树结构不平衡(通常我们删除节点总是对右子树的最小值节点替代操作&#xff0c;而不是交替的利用左子树的最大值节点替代&#xff0c;这就将导致左子树的平均深度大于右子树平均深度&a…

基于Java的高校校园点餐系统

开头语&#xff1a; 你好&#xff0c;我是计算机专业的学长&#xff0c;如果你对高校校园点餐系统感兴趣或有相关开发需求&#xff0c;欢迎联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;Eclipse、Tomcat 系统展示…

分布式用例执行

前言 这两天趁着有时间&#xff0c;我疯狂的码字了~~ 背景 我们公司是做人工智能平台的&#xff0c;什么是人工智能呢&#xff1f; 大数据 机器学习。大数据运行的基本就不快。机器学习算法运行起来也是慢的让人泪流满面。在我们的集群配置下&#xff0c;我使用一个 5M 的数…

基于IDEA的Maven(properties属性配置)

&#xff08;property &#xff1a;财产&#xff09;properties&#xff1a;它的复数。 同样也是基于上篇博客进行学习。&#xff08;具体的全部项目代码和结构可以去查看上篇...&#xff09; <properties><!--当前jdk版本 , 这一步可以完全省略--><maven.com…

2024青海三支一扶招1910人7月6日笔试

&#x1f4e2;2024年青海省三支一扶计划招募1910人公告已发布&#xff01; 小&#x1f004;️帮大家整理好了考试关键时间点&#xff1a; ★ 报名时间&#xff1a;6月20日至6月25日 ★ 报名网站&#xff1a;青海省人事考试信息网&#xff08;www.qhpta.com&#xff09; ★ 网上…

android studio构建项目报错Could not create an instance of type com.android.build.api.variant.impl.Applicat

Could not create an instance of type com.android.build.api.variant.impl.ApplicationVariantImpl 这个错误通常是由于Gradle插件版本不兼容导致的。你可能正在使用的Gradle插件版本与你的Android Studio版本不兼容。 要解决这个问题&#xff0c;你可以尝试以下解决方法&a…

音视频入门基础:H.264专题(3)——EBSP, RBSP和SODB

音视频入门基础&#xff1a;H.264专题系列文章&#xff1a; 音视频入门基础&#xff1a;H.264专题&#xff08;1&#xff09;——H.264官方文档下载 音视频入门基础&#xff1a;H.264专题&#xff08;2&#xff09;——使用FFmpeg命令生成H.264裸流文件 音视频入门基础&…

行为树行为树行为树

行为树由一个个节点组成 结构&#xff1a;树状结构运行流程&#xff1a;从根节点开始自顶向下往下遍历&#xff0c;每经过一个节点就执行节点对应的功能。 我们规定&#xff0c;每个节点都提供自己的excute函数&#xff0c;返还执行失败/成功结果。 然后根据不同节点的执行结…

国产数据库中读写分离实现机制

在数据库高可用架构下会存在1主多备的部署&#xff0c;备节点可以根据业务场景分发一部分流量以充分利用资源&#xff0c;并减轻主库的压力&#xff0c;因此在数据库的功能上需要读写分离来实现。 充分利用备节点的资源&#xff0c;提升业务的吞吐量&#xff1b;防止运维等非业…

《算法设计与分析》第五六章:回溯法与分支限界法

文章目录 回溯法分支限界法一、本章作业1.活动安排问题2.旅行商问题3.单源最短路径4.任务分配问题 二、算法积累1.回溯法求解01背包问题2.回溯法求解最大团问题3.回溯法求解n皇后问题4.回溯法求解地图着色5.回溯法求解哈密尔顿图6.回溯法求活动安排7.分支限界法求01背包问题8.分…

Flutter第十三弹 路由和导航

目标&#xff1a; 1.Flutter怎么创建路由&#xff1f; 2.怎么实现路由跳转&#xff1f;页面返回&#xff1f; 一、路由 1.1 什么是路由&#xff1f; 路由(Route)在移动开发中通常指页面&#xff08;Page&#xff09;&#xff0c;在Android中通常指一个Activity。所谓路由管…

小功率电机驱动方案中如何选择驱动IC

小功率电机驱动方案及驱动IC的选择 电机驱动作为工业4.0中工厂自动化整个闭环中的执行器环节&#xff0c;其性能好坏直接影响到整个闭环的性能。因此&#xff0c;工业4.0对电机驱动提出了更高的性能和功能要求&#xff0c;例如更快的响应速度、更高的带宽、更高精度的位置和速…