【问题标题】:Controlling QML UI from a C++ QStateMachine从 C++ QStateMachine 控制 QML UI
【发布时间】:2020-04-04 05:36:42
【问题描述】:

我正在对应用程序进行原型设计,并且我想从应用程序的 C++ 端的 QStateMachine 控制 QML UI 转换。为了让事情变得更简单,我们可以说 QML UI 由多个页面组成,其中包含应该触发从一个页面转换到另一个页面的按钮。

// main.qml
Window {

    // ..
        StackLayout {
            id: layout
            anchors.fill: parent
            currentIndex: uiController.currentPage // binding with the C++ side

            Page0 {
                id: page0
            }
            Page1 {
                id: page1
            }
            Page2 {
                id: page2
            }
            Page3 {
                id: page3
            }
        }
    // ..
    }
}

现在每个Page 都有一个Button 以便用户可以转到另一个页面:

// example of Page0.qml 
Page {
    id: root

    // ..    

    Button {
        text: "Page 1"
        width: 100
        height: 100
        anchors.top: text.bottom
        anchors.horizontalCenter: text.horizontalCenter
        anchors.horizontalCenterOffset: 10
        onClicked: {
            console.log("Button clicked")
            backend.msg = "Button clicked !"
            uiController.buttonClicked = 1; // binding with the C++ side
        }
    }
    // ..      
}

在 C++ 方面,我有一个控制器,它在内部使用状态机来控制转换:

class UIController : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int buttonClicked READ buttonClicked WRITE setButtonClicked NOTIFY buttonClickedChanged)
    Q_PROPERTY(int currentPage READ currentPage WRITE setCurrentPage NOTIFY currentPageChanged)

public:
    // ..

private:
    QStateMachine m_machine;
    int m_buttonClicked;
    int m_currentPage;
};

现在重要的部分是 QStateMachine 的设置:

UIController::UIController()
    : m_buttonClicked(0)
{   
    QState *page1 = new QState();
    QState *page2 = new QState();
    QState *page3 = new QState();
    QState *page4 = new QState();
    // ButtonTransition rely on a ButtonEvent 
    ButtonTransition *tr1 = new ButtonTransition(1);
    ButtonTransition *tr2 = new ButtonTransition(2);
    ButtonTransition *tr3 = new ButtonTransition(3);
    ButtonTransition *tr4 = new ButtonTransition(4);

    // the current page is a state property
    page1->assignProperty(this, "currentPage", 0);
    page2->assignProperty(this, "currentPage", 1);
    page3->assignProperty(this, "currentPage", 2);
    page4->assignProperty(this, "currentPage", 3);

    tr1->setTargetState(page2);
    tr2->setTargetState(page3);
    tr3->setTargetState(page4);
    tr4->setTargetState(page1);

    page1->addTransition(tr1);
    page2->addTransition(tr2);
    page3->addTransition(tr3);
    page4->addTransition(tr4);

    m_machine.addState(page1);
    m_machine.addState(page2);
    m_machine.addState(page3);
    m_machine.addState(page4);
    m_machine.setInitialState(page1);

    m_machine.start();
}

最后要发生转换:

/* this setter function is called everytime the QML side change the
   buttonClicked property of the UiController */
void UIController::setButtonClicked(int button)
{
    if (m_buttonClicked != button) {
        m_buttonClicked = button;
        m_machine.postEvent(new ButtonEvent(button));
        emit buttonClickedChanged();
    }
}

它确实有效,但我想问是否有更好的方法来做到这一点:我认为这种方法有点“笨拙”。

特别是可以将状态机转换直接绑定到 QML 信号吗? (至于QSignalTransition

谢谢。

【问题讨论】:

  • “至于QSignalTransition” - 你为什么不用那个?
  • 我不确定我是否正确...我会尝试回答。
  • 好的,我想我现在遇到了你的问题:你想要那个信号buttonClickedChanged,但你只想转换,当m_buttonClicked 具有正确的值时?还是您更愿意将buttonClickedChangedm_buttonClicked 一起根除?
  • 这是我已经拥有的:只有当ButtonEvent 匹配在ButtonTransition 的ctor 中传递的按钮值时才会触发ButtonTransition。现在我的问题与 1 更相关 - 有更好的设计吗? 2 - 如果可能,使用从 QML 对象触发的信号作为状态机的转换是否更好?
  • 您应该看看 QML 声明性状态机。它非常简洁,比类似的 QStateMachine 更容易阅读。您的 C++ 状态机可以用 25 行代码编写。

标签: c++ qt qml


【解决方案1】:

特别是可以将状态机转换直接绑定到 QML 信号吗?

是的。您可以将entered() 信号从任何子状态连接到例如buttonClickedChanged().

【讨论】:

    【解决方案2】:

    在 QML 项目中使用 declarative state machine framework 而不是 QStateMachine。它简洁易读,并且具有 QStateMachine 中不可用的功能,例如 TimeoutTransition

    您的示例 QML 代码只需少量更改即可使用声明性状态机。

    向您的“页面”组件添加顶级信号:

    // Page1.qml
    import QtQuick 2.0
    import QtQuick.Controls 2.12
    
    Page {
        id: root
    
        signal clicked()                      // <- new signal
    
        // ..
    
        Button {
            text: "Page 1"
            width: 100; height: 100
            onClicked: {
                console.log("Button clicked")
                root.clicked()                // emit new signal
            }
        }
        // ..
    }
    

    这是带有声明性状态机的整个 main.qml:

    // main.qml
    import QtQuick 2.12
    import QtQuick.Controls 2.5
    import QtQuick.Layouts 1.12
    import QtQml.StateMachine 1.12 as DSM
    
    ApplicationWindow {
        visible: true
        width: 640; height: 480
    
        StackLayout {
            id: layout
            anchors.fill: parent
    
            Page0 { id: page0 }
            Page1 { id: page1 }
            Page2 { id: page2 }
            Page3 { id: page3 }
        }
    
        DSM.StateMachine {
            initialState: p0
            running: true
    
            DSM.State {
                id: p0
                onEntered: layout.currentIndex = 0
                DSM.SignalTransition { targetState: p1; signal: page0.clicked }
            }
            DSM.State {
                id: p1
                onEntered: layout.currentIndex = 1
                DSM.SignalTransition { targetState: p2; signal: page1.clicked }
            }
            DSM.State {
                id: p2
                onEntered: layout.currentIndex = 2
                DSM.SignalTransition { targetState: p3; signal: page2.clicked }
            }
            DSM.State {
                id: p3
                onEntered: layout.currentIndex = 3
                DSM.SignalTransition { targetState: p0; signal: page3.clicked }
            }
        }
    }
    

    【讨论】:

    • OP 需要来自 C++ 端的控制
    • 我明白了;我没有用 C++ 解决方案回答 OP 的问题。但是,由于 OP 使用 QML 和 C++,我强烈建议放弃 C++ QStateMachine 并采用 QtQml.StateMachine 解决方案,因为 QML 状态机实现版本更具可读性和简洁性。
    猜你喜欢
    • 1970-01-01
    • 2016-10-05
    • 1970-01-01
    • 2020-10-21
    • 1970-01-01
    • 2020-03-16
    • 1970-01-01
    • 2017-05-29
    • 1970-01-01
    相关资源
    最近更新 更多