I want to access a C class (signals and slots) in all my qml files. When I set up a Connection in main.qml, I am able to receive the signal. However, in any other qml file (MainMenu.qml here), I can not access the signal. I can send from other qml files using slots functions, but not read the signals. Any idea how to fix it? I am very new to QML.
Main.cpp
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
Game game;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("_game", &game);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
main.qml
Window {
id: root
width: 1240
height: 800
visible: true
title: qsTr("Horse Race")
color: "black"
Image {
id: backgroundImage
anchors.fill: parent
source: "Imgs/Background.png"
}
Loader {
id: mainLoader
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
horizontalCenterOffset: - 100
}
source: "MainMenu.qml"
}
Connections {
target: _game
onNameUpdate: {
console.log("updated name")
}
onStartedNewGame: {
console.log("new game")
}
}
}
MainMenu.qml
Rectangle {
Text {
id: header
anchors.horizontalCenter: root.horizontalCenter
anchors.horizontalCenterOffset: 35
color: "white"
font.family: "Super Mario Bros."
font.pointSize: 30
text: "Game Menu"
}
Connections {
target: _game
onStartedNewGame: {
console.log("inside MainMenu")
header.color = "blue"
}
}
}
CodePudding user response:
There are two ways that I'm aware of.
- The
Game
class is aQObject
. Instead of instantiating it in your C code and callingsetContextProperty
on therootContext
, it's better to registerGame
instance as a QMLsingleton
object. You will then have access to it wherever you import it. Here is an example (TestClass will be your Game class):
#include <QQmlApplicationEngine>
#include <QCoreApplication>
#include <QDebug>
class TestClass : public QObject
{
Q_OBJECT
public:
explicit TestClass(QQmlEngine* engine, QObject *parent = nullptr);
Q_INVOKABLE void triggerSignal() { emit aSignal(); } ;
private:
QQmlEngine * m_engine;
signals:
void aSignal();
public slots:
void aSlot() { qDebug() << "aSlot invoked" };
};
static QObject *InstantiateTestClass(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(scriptEngine)
TestClass *singletonClass = new TestClass(engine);
return singletonClass;
}
static void registerTestClassSingleton()
{
qmlRegisterSingletonType<TestClass>("Globals", 1, 0, "TestClass", InstantiateTestClass);
}
Q_COREAPP_STARTUP_FUNCTION(registerTestClassSingleton)
and in every QML file you can:
...
import Globals 1.0
Item {
...
Connections {
target: TestClass
onASignal: {
console.log("A Signal triggered")
}
}
}
Be aware that the Game
class will be instantiated, when you refer to it for the first time. Creating a Connection
won't invoke the InstantiateTestClass
function. You should either call a function on TestClass, read a property or write a property to create the singleton instance. So, it's a good idea to create TestClass instance when the Window object is created like:
//main.qml
...
Windows{
...
Component.onCompleted: {
//Just to create TestClass instance. You can also refer to a property instead of calling a method on it.
//Refering to a propery is enough for the TestClass to be instantiated.
TestClass.aSlot();
}
}
- You can also create a QML singleton type, create a
var
property in it and then set thatvar
to_game
in yourmain.qml
. Then you will be able to access that member from every QML file. You should create aqmldir
file beside that QML singleton file.
//GameHelper.qml
pragma Singleton
...
QtObject{
property var gameInstance
}
//main.qml
...
import "."
Window{
...
Component.onCompleted: {
GameHelper.gameInstance = _game;
}
}
//MainMenu.qml
...
Item{
Connections {
target: GameHelper.gameInstance
onASignal: {
console.log("A Signal triggered")
}
}
}
Both methods will work. I will prefer the first one.