Qt状态机用法详解(附带实例)
Qt 的状态机框架提供了一些类来创建和执行状态图,状态图为一个系统如何对外界激励进行反应提供了一个图形化模型,该模型是通过定义一些系统可能进入的状态以及系统怎样从一个状态切换到另一个状态来实现的。
事件驱动的系统(比如 Qt 应用程序)的一个关键特性就是它的行为不仅仅依赖于最后一个事件或者当前的事件,而且也依赖于将要执行的事件。通过使用状态图,这些信息会非常容易进行表达。
状态机框架提供了一个 API 和一个执行模型来有效地将状态图的元素和语义嵌入 Qt 应用程序中。该框架与 Qt 的元对象系统是紧密结合的,例如状态间的切换可以由信号来触发。
Qt 的事件系统用来驱动状态机。在状态机框架中的状态图是分层的,一个状态可以嵌套在其他状态中,在状态机的一个有效配置中的所有状态都拥有一个共同的祖先。
下图是该状态机的状态图:

图 1 一个简单的状态机的状态图
下面通过编写代码来看一下该状态机在程序中的实现。新建 Empty qmake Project,项目名称为 mystatemachine,完成后先在项目文件中添加 QT += widgets statemachine 一行代码并保存该文件,然后添加新的 main.cpp 文件,并更改其中内容为:
状态机是异步执行的,它会成为应用程序事件循环的一部分。现在可以运行程序,然后单击按钮,查看状态机的运行效果。
当状态机进入一个状态时,会发射 QState::entered() 信号,而退出一个状态时,会发射 QState:: exited() 信号。可以关联这两个信号来完成一些操作。
例如在进入 s3 状态时将按钮最小化,那么可以在程序中调用 setInitialState() 函数的代码前添加如下代码:
这里无须为动画设置开始和结束的值,它们会被隐含地进行设置,开始值就是开始播放动画时属性的当前值,结束值就是状态分配的属性的值。
事件驱动的系统(比如 Qt 应用程序)的一个关键特性就是它的行为不仅仅依赖于最后一个事件或者当前的事件,而且也依赖于将要执行的事件。通过使用状态图,这些信息会非常容易进行表达。
状态机框架提供了一个 API 和一个执行模型来有效地将状态图的元素和语义嵌入 Qt 应用程序中。该框架与 Qt 的元对象系统是紧密结合的,例如状态间的切换可以由信号来触发。
Qt 的事件系统用来驱动状态机。在状态机框架中的状态图是分层的,一个状态可以嵌套在其他状态中,在状态机的一个有效配置中的所有状态都拥有一个共同的祖先。
Qt创建状态机
下面先来看一个简单的应用,假定状态机由一个 QPushButton 控制,包含 3 个状态:s1、s2 和 s3。其中,s1是初始状态。当单击按钮时,状态机切换到另一个状态。下图是该状态机的状态图:

图 1 一个简单的状态机的状态图
下面通过编写代码来看一下该状态机在程序中的实现。新建 Empty qmake Project,项目名称为 mystatemachine,完成后先在项目文件中添加 QT += widgets statemachine 一行代码并保存该文件,然后添加新的 main.cpp 文件,并更改其中内容为:
#include <QApplication> #include <QPushButton> #include <QState> #include <QStateMachine> int main(int argc, char* argv[ ]) { QApplication app(argc, argv); QPushButton button("State Machine"); // 创建状态机和3个状态,并将3个状态添加到状态机中 QStateMachine machine; QState *s1 = new QState(&machine); QState *s2 = new QState(&machine); QState *s3 = new QState(&machine); // 为按钮部件的geometry属性分配一个值,当进入该状态时会设置该值 s1->assignProperty(&button, "geometry", QRect(100, 100, 120, 50)); s2->assignProperty(&button, "geometry", QRect(300, 100, 120, 50)); s3->assignProperty(&button, "geometry", QRect(200, 200, 120, 50)); // 使用按钮部件的单击信号来完成3个状态的切换 s1->addTransition(&button, &QPushButton::clicked, s2); s2->addTransition(&button, &QPushButton::clicked, s3); s3->addTransition(&button, &QPushButton::clicked, s1); // 设置状态机的初始状态并启动状态机 machine.setInitialState(s1); machine.start(); button.show(); return app.exec(); }
- 要使用一个状态机,需要先创建该状态机和使用的状态,可以像这里在创建状态时直接将其添加到状态机中,也可以使用 QStateMachine::addState() 来添加状态;
- 创建完状态后,要使用 assignProperty() 函数为 QObject 对象的属性分配值,这样在进入该状态时就可以为 QObject对象的这个属性设置该值;
- 然后要使用 addTransition() 函数来完成一个状态到另一个状态的切换,可以关联 QObject 对象的一个信号来触发切换;
- 最后要为状态机设置初始状态并启动状态机,这样当状态机启动时就会自动进入初始状态。
状态机是异步执行的,它会成为应用程序事件循环的一部分。现在可以运行程序,然后单击按钮,查看状态机的运行效果。
当状态机进入一个状态时,会发射 QState::entered() 信号,而退出一个状态时,会发射 QState:: exited() 信号。可以关联这两个信号来完成一些操作。
例如在进入 s3 状态时将按钮最小化,那么可以在程序中调用 setInitialState() 函数的代码前添加如下代码:
QObject::connect(s3, &QState::entered, &button, &QPushButton::showMinimized);这里定义的 3 个状态间的切换是循环的,状态机也永远不会停止。如果想让状态机完成一个状态后就停止,那么可以设置这个状态为 QFinalState 对象,将它加入状态图中,等切换到该状态时,状态机就会发射 finished() 信号并停止。
Qt状态机中使用动画
如果将状态机中的 API 和 Qt 中的动画 API 相关联,那么就可以使分配到状态上的属性自动实现动画效果。在前面的程序中先添加头文件:#include <QSignalTransition> #include <QPropertyAnimation>然后将进行状态切换的代码更改如下:
QSignalTransition *transition1 = s1->addTransition(&button, &QPushButton::clicked, s2); QSignalTransition *transition2 = s2->addTransition(&button, &QPushButton::clicked, s3); QSignalTransition *transition3 = s3->addTransition(&button, &QPushButton::clicked, s1); QPropertyAnimation *animation = new QPropertyAnimation(&button, "geometry"); transition1->addAnimation(animation); transition2->addAnimation(animation); transition3->addAnimation(animation);这样就可以在状态切换时使用动画效果了。在属性上添加动画,就意味着当进入一个状态时分配的属性将无法立即生效,而是在进入时开始播放动画,然后以平滑的动画来达到属性分配的值。
这里无须为动画设置开始和结束的值,它们会被隐含地进行设置,开始值就是开始播放动画时属性的当前值,结束值就是状态分配的属性的值。