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 的初始子状态时s11
        s1->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 的初始子状态时s11
        s1->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); // 中止状态


    // 设置状态机初始状态s1
    machine.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
  \overload

  Adds a transition associated with the given \a signal of the given \a sender
  object, and returns the new QSignalTransition object. The transition has
  this 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 sender
  object, and returns the new QSignalTransition object. The transition has
  this 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-level
  state and the state machine takes ownership of the state.

  If the state is already in a different machine, it will first be removed
  from 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JUC之可重入锁

&#x1f4d1;前言 本文主要是【JUC】——JUC之可重入锁的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&…

.NET 8.0 发布到 IIS

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

【Docker】在Windows操作系统安装Docker前配置环境

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Docker容器》序列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…

利用Lambda表达式实现vector中pair/结构体的排序

众所周知&#xff0c;对于vector<pair<int, int> >若直接使用sort排序&#xff0c;会默认按照pair的第一个关键字从小到大进行排序&#xff1a; #include <bits/stdc.h>using namespace std;int main() {vector<pair<int, int> > p;p.push_back…

CentOS 7 权限管理实战指南:用户管理相关命令详解

前言 掌握 CentOS 7 用户管理命令&#xff0c;轻松管理系统用户&#xff01;本文详细介绍了在 CentOS 7 系统中常用的用户管理命令&#xff0c;从创建和删除用户、修改用户属性&#xff0c;到密码管理和用户权限设置&#xff0c;一应俱全。无论你是 Linux 新手还是经验丰富的管…

Python 网络编程之粘包问题

【一】粘包问题介绍 【1】粘包和半包 粘包&#xff1a; 定义&#xff1a; 粘包指的是发送方发送的若干个小数据包被接收方一次性接收&#xff0c;形成一个大的数据包。原因&#xff1a; 通常是因为网络底层对数据传输的优化&#xff0c;将多个小数据包组合成一个大的数据块一次…

Ubantu 安装vscode配置c/c++环境

文章目录 安装VSCode注意 snap包冲突 安装C/C编译环境注意 进程锁占用 配置C开发环境安装插件配置tasks.json配置c_cpp_properties.json 配置调试环境配置 launch.json 安装VSCode 方式一&#xff1a;ubantu 软件里面直接安装 方式二&#xff1a;官网下载deb安装包https://cod…

Angular系列教程之zone.js和NgZone

文章目录 什么是zone.jsZone的工作原理Zone的常见用途NgZone&#xff1a;Angular中的zone.js使用NgZone使用NgZone执行代码使用NgZone外部检测 结论 什么是zone.js 在Angular中&#xff0c;zone.js是一个非常重要的库&#xff0c;它为我们提供了一种跟踪和管理异步操作的机制。…

【STM32】STM32学习笔记-USART串口收发HEX和文本数据包(29)

00. 目录 文章目录 00. 目录01. 串口简介02. 串口收发HEX数据包接线图03. 串口收发HEX数据包示例104. 串口收发HEX数据包示例205. 串口收发文本数据包接线图06. 串口收发文本数据包示例07. 程序示例下载08. 附录 01. 串口简介 串口通讯(Serial Communication)是一种设备间非常…

AI与区块链的完美交融创新时代的双重引擎

每个投资者都梦想早日进入“下一个亚马逊、苹果或比特币”&#xff0c;以追求代际财富。 然而&#xff0c;这些机会很少而且相距甚远&#xff0c;而且正如每一个虔诚的加密货币本地人都知道的那样&#xff0c;这条道路上常常布满了失败的项目、失信的承诺和波动。 但在 2023 …

最新版git2.43安装、记住用户名和密码以及tortoisegit2.15使用

一、下载git 打开git官网地址&#xff1a;https://git-scm.com/进行下载 下载完安装&#xff0c;一直next就好&#xff0c;如果愿意就可以改下安装路径&#xff0c;改在d盘。 具体可以参考&#xff1a;git安装教程 二、安装完下载小乌龟以及中文语言包 下载地址&#xff1a;…

电脑本地连接不见了怎么恢复?5个方法轻松解决问题!

“我在使用电脑时&#xff0c;突然发现我的本地连接不见了&#xff0c;这是怎么回事呢&#xff1f;有什么方法可以解决这个问题吗&#xff1f;” 电脑的本地连接是一种将电脑与局域网连接的方式。局域网是一种小型的网络&#xff0c;通常在建筑物内或地理位置相近的少量计算机之…

【Web】CTFSHOW PHP特性刷题记录(全)

知其然知其所以然&#xff0c;尽量把每种特性都详细讲明白。 目录 web89 web90 web91 web92 web93 web94 web95 web96 web97 web98 web99 web100 web101 web102 web103 web104 web105 web106 web107 web108 web109 web110 web111 web112 web113 web…

轻松识别Midjourney等AI生成图片,开源GenImage

AIGC时代&#xff0c;人人都可以使用Midjourney、Stable Diffusion等AI产品生成高质量图片&#xff0c;其逼真程度肉眼难以区分真假。这种虚假照片有时会对社会产生不良影响&#xff0c;例如&#xff0c;生成公众人物不雅图片用于散播谣言&#xff1b;合成虚假图片用于金融欺诈…

Ubuntu20.4 Mono C# gtk 编程习练笔记(一)

简言 Mono是Linux环境下C#的开发、编译及运行环境。gtk是gnome独具特色的图形库&#xff0c;Mono对它进行了C#封装。Linux环境下&#xff0c;许多的编程语言使用gtk界面库&#xff0c;有比较好的编程群众基础。另外&#xff0c;Mono相对于DOTNET来说要轻量许多&#xff0c;它们…

多输入多输出 | Matlab实现PSO-CNN粒子群优化卷积神经网络多输入多输出预测

多输入多输出 | Matlab实现PSO-CNN粒子群优化卷积神经网络多输入多输出预测 目录 多输入多输出 | Matlab实现PSO-CNN粒子群优化卷积神经网络多输入多输出预测预测效果基本介绍模型背景程序设计参考资料 预测效果 基本介绍 Matlab实现PSO-CNN粒子群优化卷积神经网络多输入多输出…

Qt/QML编程之路:使用camera摄像头(35)

汽车应用中,camera起到了越来越多的作用,数字化的作用,这点无可争议,而作为GUI设计工具,如何让Camera类的应用能更好的发挥作用呢? You can use Camera to capture images and movies from a camera, and manipulate the capture and processing settings that get appl…

Docker网络配置网络模式

前言 Docker 的网络模式是一种定义容器如何在网络中通信的方式。Docker 提供了多种网络模式&#xff0c;每种模式都适用于不同的使用场景 一.网络相关概念 1.子网掩码 互联网是由许多小型网络构成的&#xff0c;每个网络上都有许多主机&#xff0c;这样便构成了一个有层次的结…

VG-4231CE(压控晶体振荡器(VCXO)微型低轮廓,宽拉范围)

爱普生晶振VG-4231CE是一款VCXO压控晶体振荡器&#xff0c;频率范围3MHz ~ 50MHz 输出频率范围不包括50MHz&#xff0c;电源电压采用 3.3V&#xff08;PSCM / CSCM&#xff09;、2.8V&#xff08;PSBM / CSBM&#xff09;或 1.8V&#xff08;PQEM / CQEM&#xff09;可满足不同…

Python-动态烟花【附完整源码】

烟花代码 运行效果&#xff1a;Python动态烟花代码 import pygame from random import randint from random import uniform from random import choice import math vector pygame.math.Vector2 # 重力变量 gravity vector(0, 0.3) # 控制窗口的大小 DISPLAY_WIDTH DISP…