PDA

View Full Version : Changing my QML thought my QStateMachine



jfdesjardins2
18th December 2016, 22:50
Hello,

I want to use QML with a QStateMachine. My StateMachine can be quite complex and I want to be able to test it. I want as little logic as possible in my QML.

I am not sure if I'm on the right way, but here is what I was thinking:
- Use a QML Main with a Loader.
- Create my QStateMachine
- On state change, load the desired page through the loader.

I have trouble with the last one. How can I tell my Qml which part to load? For example, let's say I have this :



Item {
id : mainWindow
visible: true
width: 1920
height: 1080

Loader { id: pageLoader
objectName: "pageLoader"
anchors.fill:parent
source: "splashScreen/SplashScreen.qml"
}
}


Then, from my C++ code, when my StateMachine tells me to change screen, I do:



QObject* QmlStartupViewBuilder::createNextScreen()
{
QObject* root = m_engine->rootObjects()[0];
QQuickItem* loaderItem = root->findChild<QQuickItem*>("pageLoader");
loaderItem->setProperty("source", QUrl(QStringLiteral("qrc:/screens/screen2/Screen2.qml")));
}


It works, but I am not sure it is the best solution. I guess I should use a slot/signal from my ViewBuilder to call my qml. But how?

Thanks a lot.

anda_skoa
19th December 2016, 10:16
The general approach is good, but instead of "directing" the QML side you provide data the QML side can react to.

Basically you combine two things:
* QML can easily and automatically react to changes in property values. This is a central concept in QML, called "property bindings"
* QStateMachine can assign values to properties when it enters state, see QState::assignProperty().

You combine that by having a mediator object, something that is known to both the state machine and the QML engine, carrying the properties that you want the state machine to control and the QML side to follow.



class AppState : public QObject
{
Q_OBJECT

// make this a QUrl type property if you need to pass the full URL
// here I assume that you pass a path relative to the QML that has the Loader
Q_PROPERTY(QString currentScreen MEMBER m_currentScreen NOTIFY currentScreenChanged)

public:
explicit AppState(QObject *parent = 0);

signals:
void currentScreenChanged();

private:
QString m_currentScreen;
};

An instance of that is passed to both QML engine setup and state machine setup


AppState *appState = new AppState();

qmlEngine->rootContext()->setContextProperty("_appState", appState);
createStateMachine(appState);


The State machine can then do things like this


QState *startState = new QState(m_stateMachine);
startState->assignProperty(m_appState, "currentScreen", "screens/screen2/Screen2.qml");


The QML side can then do things like


Loader {
source: _appState.currentScreen
}


A very similar technique can be done for the other way aroound, when the QML side needs to trigger state transitions.
You either use an additional interface object or extend the AppState class.



public slots:
void reset() { emit triggerReset(); }

signals:
void triggerReset();


The state machine can use that as a source for transitions


runningState->addTranstion(m_appState, SIGNAL(triggerReset()), startState);


The QML side can ask for that


MouseArea {
onClicked: _appState.reset();
}
// or
Keys.onEscapePressed: _appState.reset()


Cheers,
_