#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:
Есть два способа, о которых я знаю.
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();
}
}
- Вы также можете создать одноэлементный тип 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
классу.