我可以在非图形应用程序中使用Qt的声明性状态机框架吗?

时间:2018-01-04 23:28:38

标签: qt qml

我真的很喜欢Qt的Declarative State Machine Framework(DSMF),但我想用它的方式(可能)有点奇怪。在过去的几年里,我一直在小型(ish)嵌入式设备的非图形应用程序中使用Qt,我最近一直在学习DSMF,它的语法我真的喜欢。

框架名称中的'declarative'限定符似乎意味着“在QML文件中声明”,如下所示:

StateMachine {
    id: stateMachine

    initialState: s1
    running: true

    State {
        id: s1

        SignalTransition {
            targetState: s2
            signal: button.clicked
        }

        // do something when the state enters/exits
        onEntered: console.log("s1 entered")
        onExited: console.log("s1 exited")
    }

    State {

        // create a transition from s2 to s3 when the button is clicked
        SignalTransition {
            targetState: s1
            signal: button.clicked
        }

        onEntered: console.log("s2 entered")
        onExited: console.log("s2 exited")
    }
}

如果我已正确理解框架,看来我可以完全在(动态加载的)QML文件中定义状态逻辑,并通过暴露C ++对象的属性来实现我需要的世界的任何副作用通过例如QQmlContext::setContextProperty()到QML。如果我是对的,我应该能够通过简单地修改QML文件来更改状态机逻辑,而无需重新编译应用程序。

我遇到的困难是我最近才开始学习QML,到目前为止我发现的大多数(所有?)示例都是用于图形应用程序。我最初认为QML只是打算用于图形应用程序,但是this SO answer指向QBS,它看起来是一个使用QML的控制台应用程序。

我现在正试图挑选QBS,看看它是否提供了关于如何在控制台应用程序中使用声明状态机框架的任何线索。但是我需要花一些时间来弄明白,我担心可能会遇到一些问题,导致无法实现我的目标。

任何人都可以确认在Qt控制台应用程序中使用DSMF是否可能? (对于指向或包含特定示例的答案,将给予额外的权重......)

1 个答案:

答案 0 :(得分:1)

由于到目前为止还没有回复,我将自己回答是否可能在非图形应用程序中使用DSMF的问题。它是!我将介绍我编写的小例子程序,以满足我的好奇心。首先,这是一个C ++类,旨在模拟一个“域对象”,它知道如何捅世界以达到某种期望的副作用 - 在这种情况下,连接到网络:

// File: NetworkManager.h
#ifndef NETWORK_MANAGER_H                                                                                                                                                                 
#define NETWORK_MANAGER_H

#include <QObject>
#include <QTimer>
#include <iostream>

class NetworkManager : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bool timedOut MEMBER timedOut_ READ getTimedOut)
public:
    NetworkManager() : timedOut_(false) { }
    ~NetworkManager() { }
    Q_INVOKABLE void connectAsync()
    {
        std::cout << "NetworkManager: In connectAsync(), connecting...\n";

        // Simulate a successful connection within 1 to 5 seconds...
        QTimer::singleShot(
            (1 + qrand() % 5)*1000,
            [this]() {
                std::cout << "NetworkManager: Connected!\n";
                emit this->connected();

                // ... and a random disconnect 5 to 15 seconds later.
                QTimer::singleShot(
                    (5 + qrand() % 11)*1000,
                    [this]() {
                        std::cout << "NetworkManager: Lost connection!\n";
                        emit this->disconnected();
                    }
                );
            }
        );
    }

    bool getTimedOut() const { return timedOut_; }

signals:
    void connected();
    void disconnected();
private:
    bool timedOut_;
};

#endif // NETWORK_MANAGER_H

这是定义状态逻辑的QML文件。请注意它如何将“真实”工作委托给域对象:

// File: NetworkManager.qml
import QtQuick 2.0                                                                                                                                                                        
import QtQml.StateMachine 1.0

StateMachine {
    id: networkFsm
    initialState: sConnecting

    running: true

    State {
        id: sConnecting

        SignalTransition {
            targetState: sConnected
            signal: network_.connected
            guard: {
                network_.timedOut == false;
            }
        }

        onEntered: {
            console.log("NetworkFsm: sConnecting entered");
            network_.connectAsync();
        }

        onExited: {
            console.log("NetworkFsm: sConnecting exited");
        }
    }

    State {
        id: sConnected

        SignalTransition {
            targetState: sConnecting
            signal: network_.disconnected
        }

        onEntered: {
            console.log("NetworkFsm: sConnected entered");
        }

        onExited: {
            console.log("NetworkFsm: sConnected exited");
        }
    }
}

最后,这是将{2}绑定在一起的main.cpp文件,以及用于构建所有内容的CMakeLists.txt文件:

// File: main.cpp
#include <QCoreApplication>                                                                                                                                                               
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <iostream>
#include "NetworkManager.h"

int main(int argc, char *argv[])
{    
    QCoreApplication app(argc, argv);
    NetworkManager network;
    QQmlApplicationEngine engine;

    QQmlContext* context = engine.rootContext();
    context->setContextProperty("network_", &network);
    engine.load(QUrl("./NetworkManagerFsm.qml"));

    return app.exec();
}
# File: CMakeLists.txt                                                                                                                                                                    
cmake_minimum_required(VERSION 3.0)
project(qmlfsm)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

find_package(Qt5Core REQUIRED)
find_package(Qt5Qml REQUIRED)

add_executable(qmlfsm main.cpp NetworkManager.h)
target_link_libraries(qmlfsm Qt5::Core Qt5::Qml)

以下是我运行它时的样子:

qml: NetworkFsm: sConnecting entered                                                                                                                                                      
NetworkManager: In connectAsync(), connecting...
NetworkManager: Connected!
qml: NetworkFsm: sConnecting exited
qml: NetworkFsm: sConnected entered
NetworkManager: Lost connection!
qml: NetworkFsm: sConnected exited
qml: NetworkFsm: sConnecting entered
NetworkManager: In connectAsync(), connecting...
NetworkManager: Connected!
qml: NetworkFsm: sConnecting exited
qml: NetworkFsm: sConnected entered
^C

所以,是的......这是可能的。是否有用还有待观察。我首先要问这个问题的动机是看看我是否可以在Qt中编写状态机更类似于我使用SMC的经验 - 这个工具已经很好地服务了我好几年了。我喜欢 SMC的.sm文件格式的简洁性,以及它没有充斥着<qt:editorinfo>标签和其他XML膨胀的事实。帝国商品交易所(DSMF)似乎触及了大部分相同的甜蜜点。时间会证明,但......我认为我更喜欢SCXML - 至少对于不太复杂的FSM而言。

  

注意

     

请理解上面发布的FSM只是拼凑在一起的“玩具”示例,以证明该框架可用于非图形应用程序。特别要注意的是timedOut类的NetworkManager属性仅用于突出显示导出到QML的QObject类的属性可用于保护条件的事实。 (在实际实施中,timedOut作为单独的SignalTransition更有意义。)

     

传递给导致转换的信号的参数也可用于保护条件,但我个人认为,当域对象的属性/方法被显式访问时,FSM“读得更好”。否则,人们想知道例如timedOut参数来自何处,并且必须打开域对象类的头文件才能确定。