Qt状态机框架介绍(一)
概述
狀態機,簡寫為FSM(Finite State Machine),狀態機由狀態寄存器和組合邏輯電路構成,能夠根據控制信號按照預先設定的狀態進行狀態轉移,是協調相關信號動作、完成特定操作的控制中心。
簡單來說,狀態機,就是負責執行各種狀態的切換。Qt狀態機的使用場景主要針對比較復雜的界面,或者需要切換不同狀態的控件,比如三態按鈕,每個狀態對應不同的樣式,如果自己做狀態管理,那就比較麻煩了。
而狀態機就是解決這種問題的,Qt中的狀態機框架為我們提供了很多的API和類,使我們能更容易的在自己的應用程序中集成狀態動畫。這個框架是和Qt的元對象系統機密結合在一起的。比如,各個狀態之間的轉換是通過信號觸發的,狀態可被配置為用來設置QObject對象的屬性以及調用其方法。可以說Qt中的狀態機就是通過Qt自身的事件系統來驅動的。
狀態機框架中的狀態圖是分層的。狀態可以嵌套在其他狀態中,狀態機的當前配置由當前活動的狀態集組成。狀態機有效配置中的所有狀態都有一個共同的祖先。
狀態機框架中的類
以下類由Qt提供,用于創建事件驅動的狀態機。
一個簡單的示例
接下來直接通過一個簡單的示例來了解Qt狀態機的工作方式。
最基礎的Qt狀態機使用流程如下:
- 創建一個狀態機QStateMachine和需要的狀態QState
- 使用QState::addTransition() 函數為這些狀態之間添加過渡
- 將狀態添加到狀態機進行管理,并為狀態機設置一個初始狀態
- 啟動狀態機
效果圖:
這里btn2有三種狀態,分別顯示在不同的位置,通過點擊Btn1進行狀態的切換。
其狀態圖如下:
接下來看看代碼:
#include <QWidget> #include <QState> #include <QStateMachine>namespace Ui { class Widget; }class Widget : public QWidget {Q_OBJECTpublic:explicit Widget(QWidget *parent = nullptr);~Widget();private slots:void onOutputMessage();private:Ui::Widget *ui;QStateMachine * m_pStateMachine = nullptr;QState * m_pState1 = nullptr;QState * m_pState2 = nullptr;QState * m_pState3 = nullptr; }; #include "widget.h" #include "ui_widget.h" #include <QDebug>Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget) {ui->setupUi(this);m_pStateMachine = new QStateMachine(this);m_pState1 = new QState();m_pState2 = new QState();m_pState3 = new QState();m_pState1->assignProperty(ui->btn2,"pos",QPoint(20,40));m_pState2->assignProperty(ui->btn2,"pos",QPoint(80,40));m_pState3->assignProperty(ui->btn2,"pos",QPoint(120,40));m_pState1->addTransition(ui->btn1,SIGNAL(clicked()),m_pState2);m_pState2->addTransition(ui->btn1,SIGNAL(clicked()),m_pState3);m_pState3->addTransition(ui->btn1,SIGNAL(clicked()),m_pState1);m_pStateMachine->addState(m_pState1);m_pStateMachine->addState(m_pState2);m_pStateMachine->addState(m_pState3);m_pStateMachine->setInitialState(m_pState1);m_pStateMachine->setInitialState(m_pState1);m_pStateMachine->start(); }Widget::~Widget() {delete ui; }這樣狀態機就開始異步的運行了,也就是說,它成為了我們應用程序事件循環的一部分。這也對應了我們上面說的,Qt的狀態機是通過Qt自身的事件機制來驅動的。
狀態轉換時操作QObject對象
使用QState::assignProperty() 函數當進入某個狀態時讓其去修改某個QObject對象的屬性。也就是上面使用的m_pState1->assignProperty(ui->btn2,"pos",QPoint(20,40)); 執行該狀態時,改變Btn2的位置屬性。
除了操作QObject對象的屬性外,我們還能通過狀態的轉換來調用QObject對象的函數。這是通過使用狀態轉換時發出的信號完成的。其中,當進入某個狀態時會發出QState::enterd() 信號,當退出某個狀態時會發出QState::exited() 信號。
connect(m_pState3,&QState::entered,this,[=](){qDebug() << __FUNCTION__ << "state3 entered..";});connect(m_pState3,&QState::exited,this,[=](){qDebug() << __FUNCTION__ << "state3 exited..";});當進入和離開m_pState3狀態時,將會輸出響應的日志。
狀態機結束
以上的示例狀態機是永遠不會停止的,每次點擊按鈕會一直循環切換不同狀態,那現在如果要停止狀態機該怎么辦呢。
為了使一個狀態機在某種條件下結束,我們需要創建一個頂層的final 狀態(QFinalState object) 。當狀態機進入一個頂層的final 狀態時,會發出finished() 信號后結束。所以,我們只需要為上面的狀態圖引入一個final 狀態,并把它設置為某個過渡的目標狀態即可。這樣當狀態機在某種條件下轉換到該狀態時,整個狀態機結束。
如果我們想讓用戶隨時通過點擊退出按鈕來退出整個應用程序。為了實現這個需求,我們需要創建一個final狀態并使他成為和按鈕的clicked()信號相關聯的那個過渡的目標狀態。有兩種方案:
- 方法一:為狀態s1,s2,s3分別添加一個到final狀態的過渡,但這看上去有點多余,代碼臃腫,并且不利于將來的擴張。
- 方法二:將狀態s1,s2,s3分成一組。我們通過創建一個新的頂層狀態并使s1,s2,s3成為其孩子來完成。下面是這種方法所對應的狀態轉換圖:
上面的三個狀態被重命名為s11,s12,s13以此來表明它們是s1的孩子。子狀態會隱式的繼承父狀態的過渡。這意味著我們目前可以只添加一個s1到final狀態s2的過渡即可,s11,s12,s13會繼承這個過渡,從而無論在什么狀態均可退出應用程序。并且,將來新添加到s1的新的子狀態也會自動繼承這個過渡。
所謂的分組,就是只需在創建狀態時為其指定一個合適的父狀態即可。當然,還需要為這組狀態指定一個初始狀態,即當s1是某個過渡的目標狀態時,狀態機應該進入哪個子狀態。實現代碼如下:
#include <QWidget> #include <QState> #include <QStateMachine> #include <QFinalState>namespace Ui { class Widget; }class Widget : public QWidget {Q_OBJECTpublic:explicit Widget(QWidget *parent = nullptr);~Widget();private slots:void onOutputMessage();private:Ui::Widget *ui;QStateMachine * m_pStateMachine = nullptr;QState * m_pState1 = nullptr;QState * m_pState2 = nullptr;QState * m_pState3 = nullptr;QState * m_pStateParent = nullptr;QFinalState * m_pFinalState = nullptr; }; #include "widget.h" #include "ui_widget.h" #include <QDebug>Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget) {ui->setupUi(this);m_pStateMachine = new QStateMachine(this);m_pStateParent = new QState();m_pState1 = new QState(m_pStateParent);m_pState2 = new QState(m_pStateParent);m_pState3 = new QState(m_pStateParent);m_pState1->assignProperty(ui->btn2,"pos",QPoint(20,40));m_pState2->assignProperty(ui->btn2,"pos",QPoint(80,40));m_pState3->assignProperty(ui->btn2,"pos",QPoint(120,40));m_pState1->addTransition(ui->btn1,SIGNAL(clicked()),m_pState2);m_pState2->addTransition(ui->btn1,SIGNAL(clicked()),m_pState3);m_pState3->addTransition(ui->btn1,SIGNAL(clicked()),m_pState1);m_pStateParent->setInitialState(m_pState1);m_pFinalState = new QFinalState();//當點擊Btn3時,停止狀態機m_pStateParent->addTransition(ui->btn3,SIGNAL(clicked()),m_pFinalState);m_pStateMachine->addState(m_pStateParent);m_pStateMachine->addState(m_pFinalState);m_pStateMachine->setInitialState(m_pStateParent);connect(m_pState3,&QState::entered,this,[=](){qDebug() << __FUNCTION__ << "state3 entered..";});connect(m_pState3,&QState::exited,this,[=](){qDebug() << __FUNCTION__ << "state3 exited..";});connect(m_pStateMachine,&QStateMachine::finished,this,[=](){qDebug() << __FUNCTION__ ;});m_pStateMachine->start(); }Widget::~Widget() {delete ui; }效果圖如下:
以上示例,當點擊Btn3時,停止狀態機。這時候再點擊Btn1就不會再有狀態切換。
以上是基礎的狀態機使用演示。
參考地址:https://doc.qt.io/qt-5/statemachine-api.html#?tdsourcetag=s_pctim_aiomsg
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的Qt状态机框架介绍(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在Mac下SVN(Cornerstone
- 下一篇: Qt状态机框架介绍(二)