Reputation: 2211
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.
Upvotes: 0
Views: 981
Reputation: 2118
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 }
}
}
}
Upvotes: 1
Reputation: 12006
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 e.g. buttonClickedChanged()
.
Upvotes: 2