简体   繁体   中英

Controlling QML UI from a C++ QStateMachine

I am prototyping an application and i would like to control QML UI transitions from a QStateMachine on the C++ side of the app. To make things easier, we can say that the QML UI consists in several pages which contains buttons that should trigger a transition from one page to another.

// 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
            }
        }
    // ..
    }
}

Now each Page has a Button so that the user can to go to another page:

// 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
        }
    }
    // ..      
}

On the C++ side i have a controller that internally use a statemachine to control the transitions :

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;
};

Now the important part is the set up of the 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();
}

And finally for the transitions to occurs :

/* 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();
    }
}

It actually works but i am asking if there are better ways to do that : i think this approach is a bit "clumsy".

Especially is it possible to bind the state machine transition directly to QML signals ? (as for QSignalTransition )

Thank you.

Especially is it possible to bind the state machine transition directly to QML signals ?

Yes. You can connect the entered() signal from any sub-state to eg buttonClickedChanged() .

Instead of QStateMachine, use the declarative state machine framework in QML projects. It is concise, readable and has features not available in QStateMachine such as the TimeoutTransition .

Your sample QML code needs few changes to use the declarative state machine.

Add a top-level signal to your "Page" components:

// 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
        }
    }
    // ..
}

And here's the entire main.qml with the declarative state machine:

// 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 }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM