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

news2024/11/16 1:38:58

一、什么是状态机框架

状态机框架提供了用于创建和执行状态图/表[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

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

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

相关文章

实人认证(人像三要素)API:加强用户身份验证

前言 在当今数字化时代&#xff0c;随着互联网应用的广泛普及&#xff0c;用户身份验证的重要性日益凸显。实人认证&#xff08;人像三要素&#xff09;API作为一种新型的身份验证方式&#xff0c;凭借其高效、安全和便捷的特性&#xff0c;正在成为加强用户身份验证的强大工具…

八、K8S metrics-server

下载yaml文件 wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/high-availability.yaml 改名&#xff1a;mv high-availability.yaml metrics-server.yaml 查看镜像地址 查看镜像地址 grep -rn image high-availability.yaml 150: …

深入浅出Spring AOP

第1章&#xff1a;引言 大家好&#xff0c;我是小黑&#xff0c;咱们今天要聊的是Java中Spring框架的AOP&#xff08;面向切面编程&#xff09;。对于程序员来说&#xff0c;理解AOP对于掌握Spring框架来说是超级关键的。它像是魔法一样&#xff0c;能让咱们在不改变原有代码的…

kylin集群负载均衡(kylin3,hbaseRIF问题)

hbase历险记 目录 hbase历险记 寻找问题 分析原因 解决方案 方案1&#xff08;资源问题、失败&#xff09; 方案2&#xff08;成功&#xff09; 寻找问题 不知道你是不是有这样的疑惑。我kylin是个单机&#xff0c;我使用的hbase是个集群&#xff0c;但内存全在某一台机…

虚拟机 以及 Centos 7的 安装全过程

目录 安装VMwere Workstion 虚拟机的操作过程 CentOS 7 安装过程 install CentOS 7 安装操作系统 安装VMwere Workstion 虚拟机的操作过程 更改安装位置 到下面图片中的这一个步骤&#xff0c;可以点击许可证&#xff0c;输入密钥就可以使用了&#xff0c; 密钥可以去某度或…

【保姆级教程|YOLOv8添加注意力机制】【2】在C2f结构中添加ShuffleAttention注意力机制并训练

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

【SpringBoot框架篇】35.kafka环境搭建和收发消息

kafka环境搭建 kafka依赖java环境,如果没有则需要安装jdk yum install java-1.8.0-openjdk* -y1.下载安装kafka kafka3.0版本后默认自带了zookeeper&#xff0c;3.0之前的版本需要单独再安装zookeeper,我使用的最新的3.6.1版本。 cd /usr/local wget https://dlcdn.apache.…

SpringBoot Redis入门(四)——Redis单机、哨兵、集群模式

单机模式&#xff1a;单台缓存服务器&#xff0c;开发、测试环境下使用&#xff1b;哨兵模式&#xff1a;主-从模式&#xff0c;提高缓存服务器的高可用和安全性。所有缓存的数据在每个节点上都一致。每个节点添加监听器&#xff0c;不断监听节点可用状态&#xff0c;一旦主节点…

x-cmd pkg | public-ip-cli - 公共 IP 地址查询工具

简介 public-ip-cli 是一个用 Javascript 编写的命令行工具&#xff0c;用于获取当前计算机或网络所使用的公共 IP 地址。 它可以让用户在命令行界面上查询 OpenDNS、Google DNS 和 HTTPS 服务的 DNS 记录以获取与互联网通信时所分配的公共 IP 地址。 首次用户 使用 x env us…

成功解决VScode进入到内置函数中调试

主要有两个关键步骤&#xff0c; 第一步 将launch.json中的"justMyCode"设为false 可通过使用ctrlshiftP搜索lauch.json找到次文件 如果找不到的话&#xff0c;可点击debug按钮&#xff0c;然后找到点击create a launch.json file创建 创建得到的launch.json如下&am…

四大软件架构:掌握单体、分布式、微服务、Serverless 的精髓

四大软件架构&#xff1a;掌握单体、分布式、微服务、Serverless 的精髓 简介&#xff1a; 如果一个软件开发人员&#xff0c;不了解软件架构的演进&#xff0c;会制约技术的选型和开发人员的生存、晋升空间。这里我列举了目前主要的四种软件架构以及他们的优缺点&#xff0c;…

isis小实验

要求: 1.合理规划level1-2 2.r1访问r5走r6且走上面 3.全网可达 个人理解:以重发布的视角:is-level level1即L1可以看做rip,L2可以看做OSPF,L1-2可以看作是既要rip又要OSPF,优点:isis只用在每个路由器上宣告一次 缺点:isis需要每个接口上输isis enable 1(序号)特点:L1-2会自动下…

Java、C#、Python间的Battle

一、编译原理和开发效率 编译速度&#xff1a; C# &#xff08;约大于等于&#xff09; JAVA > Python python的编译原理 前提&#xff1a;python 3.6 python不会直接编译源码 而是把源码直接扔给解释器&#xff0c;这种方式 使得python非常灵活&#xff0c;让它的开发效…

Docker Consul详解与部署示例

目录 Consul构成 Docker Consul 概述 Raft算法 服务注册与发现 健康检查 Key/Value存储 多数据中心 部署模式 consul-template守护进程 registrator容器 consul服务部署&#xff08;192.168.41.31&#xff09; 环境准备 搭建Consul服务 查看集群信息 registrato…

uniCloud ---- uni-captch实现图形验证码

目录 用途说明 组成部分 目录结构 原理时序 云端一体组件介绍 验证码配置&#xff08;可选&#xff09;&#xff1a; 普通验证码组件 公共模块 云函数公用模块 项目实战 创建云函数 创建注册页 创建云函数 关联公用模块 uni-captcha 刷新验证码 自定义实现 验…

Go新项目-为何选Gin框架?(0)

先说结论&#xff1a;我们选型Gin框架 早在大概在2019年下旬&#xff0c;由于内部一个多线程上传的需求&#xff0c;考虑到Go协程的优势&#xff1b; 内部采用Gin框架编写了内部的数据上传平台BAP&#xff0c;采用GinVue开发&#xff0c;但前期没考虑到工程化思维&#xff0c;导…

用LED数码显示器伪静态显示数字1234

#include<reg51.h> // 包含51单片机寄存器定义的头文件 void delay(void) //延时函数&#xff0c;延时约0.6毫秒 { unsigned char i; for(i0;i<200;i) ; } void main(void) { while(1) //无限循环 { P20xfe; …

.Net 8.0 Web API Controllers 添加到 windows 服务

示例源码下载&#xff1a;https://download.csdn.net/download/hefeng_aspnet/88747022 创建 Windows 服务的方法之一是从工作线程服务模板开始。 但是&#xff0c;如果您希望能够让它托管 API 控制器&#xff08;也许是为了查看它正在运行的进程的状态&#xff09;&#xff0…

IC验证——perl脚本ccode_standard——c代码寄存器配置标准化

目录 1 脚本名称 2 脚本路径 3 脚本参数说明 4 脚本操作说明 5 脚本代码 1 脚本名称 ccode_standard 2 脚本路径 /scripts/bin/ccode_standard 3 脚本参数说明 次序 参数名 说明 1 address (./rfdig&#xff1b;.&#xff1b;..&#xff1b;./boot) 指定脚本执行路…

如何避免知识付费小程序平台的陷阱?搭建平台的最佳实践

随着知识经济的兴起&#xff0c;知识付费已经成为一种趋势。越来越多的人开始将自己的知识和技能进行变现&#xff0c;而知识付费小程序平台则成为了一个重要的渠道。然而&#xff0c;市面上的知识付费小程序平台琳琅满目&#xff0c;其中不乏一些不良平台&#xff0c;让老实人…