Home > Mobile >  Access C signals/slots globally in all QML files
Access C signals/slots globally in all QML files

Time:09-27

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.

  1. The Game class is a QObject. Instead of instantiating it in your C code and calling setContextProperty on the rootContext, it's better to register Game instance as a QML singleton 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();
}
}
  1. You can also create a QML singleton type, create a var property in it and then set that var to _game in your main.qml. Then you will be able to access that member from every QML file. You should create a qmldir 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.

  • Related