Доступ к сигналам/слотам C по всему миру во всех файлах QML

#c #qt #qml #signals-slots

Вопрос:

Я хочу получить доступ к классу C (сигналы и слоты) во всех моих файлах qml. Когда я устанавливаю соединение в main.qml, я могу принимать сигнал. Однако в любом другом файле qml (здесь MainMenu.qml) я не могу получить доступ к сигналу. Я могу отправлять из других файлов qml, используя функции слотов, но не считывать сигналы. Есть идеи, как это исправить? Я очень новичок в 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", amp;game);


    const QUrl url(QStringLiteral("qrc:/main.qml"));


    QObject::connect(amp;engine, amp;QQmlApplicationEngine::objectCreated,
                     amp;app, [url](QObject *obj, const QUrl amp;objUrl) {
        if (!obj amp;amp; 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")
        }
    }
}
 

Главное меню.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"
        }
    }
}
 

Комментарии:

1. На самом деле, это должно работать как есть. Я полагаю, никаких предупреждений? Может быть, вы можете попробовать удалить подчеркивание из имени?

2. Пошел дальше и попробовал ваш код, я воспроизвел его по желанию в Qt 5.12.5 (меню становится синим)

Ответ №1:

Есть два способа, о которых я знаю.

  1. Game Класс-это а QObject . Вместо того , чтобы создавать его в коде C и вызывать setContextProperty rootContext , лучше зарегистрировать Game экземпляр как singleton объект QML. Затем у вас будет доступ к нему, куда бы вы его ни импортировали. Вот пример (тестовый класс будет вашим игровым классом):
 #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)
 

и в каждом файле QML вы можете:

 ...
import Globals 1.0

Item {
...
    Connections {
        target: TestClass
        onASignal: {
            console.log("A Signal triggered")
        }
    }

}
 

Имейте в виду, что Game экземпляр класса будет создан, когда вы обратитесь к нему в первый раз. Создание a Connection не вызовет InstantiateTestClass функцию. Вы должны либо вызвать функцию в TestClass, прочитать свойство, либо написать свойство для создания одноэлементного экземпляра. Таким образом, рекомендуется создать экземпляр TestClass, когда объект Window создается следующим образом:

 //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. Вы также можете создать одноэлементный тип QML, создать var в нем свойство, а затем установить его var _game в свой main.qml . Затем вы сможете получить доступ к этому участнику из каждого файла QML. Вы должны создать qmldir файл рядом с этим одноэлементным файлом QML.
 //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")
        }
    }

}
 

Оба метода будут работать. Я предпочту первый вариант.

Комментарии:

1. Спасибо за ответ, я собираюсь попробовать. Пришлось бы мне делать что-то особенное в main.cpp, например, с двигателями?

2. @sjokkopudd Нет. Вам нужно будет удалить Game экземпляр из вашего main.cpp. Если вы внимательно посмотрите на TestClass него, вы увидите макрос под названием Q_COREAPP_STARTUP_FUNCTION . Это вызовет registerTestClassSingleton правильное время QCoreApplication создания и зарегистрирует TestClass тип QML. Это спасет вашу main.cpp от загрязнения многими регистрациями типов QML и делает ваш класс более переносимым.

3. Мне все еще нужен QObject::connect()?

4. @sjokkopudd да, ты знаешь. Это связано с загрузкой main.qml. Это не имеет никакого отношения к Game классу.