传送门:
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、状态机需设置默认顶层状态
[参考文档]:
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