Problema:
Tenho um QQuickWidget dentro de um QWidget e estou adicionando esse widget a uma QMdiArea. Estou tentando chamar uma função QML (plusBtn())
de C++ usando QMetaObject::invokeMethod
. No entanto, meu aplicativo trava quando tento invocar o método. Essa trava acontece somente quando a janela é adicionada à QMdiArea. Quando removo o widget da QMdiArea, tudo funciona bem.
Aqui está meu código simplificado:
QML (Mapa.qml):
import QtQuick
import QtLocation
import QtPositioning
Rectangle {
id: rectangle
Plugin {
id: mapPlugin
name: "osm"
}
Map {
id: mapView
anchors.fill: parent
plugin: mapPlugin
zoomLevel: 15
minimumZoomLevel: 2
activeMapType: supportedMapTypes[0]
}
function plusBtn() {
if (mapView.zoomLevel < mapView.maximumZoomLevel) {
mapView.zoomLevel = Math.round(mapView.zoomLevel + 1);
}
}
}
C++ (Implementação MainWindow):
#include <QDebug>
#include <QMainWindow>
#include <QPushButton>
#include <QQmlEngine>
#include <QQuickItem>
#include <QQuickWidget>
#include <QVBoxLayout>
#include <QWidget>
#include <QApplication>
#include <QMDIArea>
class QmlWindow : public QWidget
{
public:
QmlWindow(QWidget* parent = nullptr)
: QWidget(parent)
{
QVBoxLayout* layout = new QVBoxLayout(this);
// Create and initialize the QQuickWidget
quickWidget = new QQuickWidget(this);
quickWidget->setSource(QUrl(QStringLiteral("qrc:/Map.qml")));
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
auto rootObj = quickWidget->rootObject();
// Create a button to invoke the QML method
QPushButton* button = new QPushButton("Call QML Method");
connect(button, &QPushButton::pressed, this, [=]() {
if (!rootObj) {
qWarning() << "Root object is null!";
return;
}
QMetaObject::invokeMethod(rootObj, "plusBtn");
});
layout->addWidget(quickWidget);
layout->addWidget(button);
setLayout(layout);
}
private:
QQuickWidget* quickWidget;
};
class MainWindow1 : public QMainWindow
{
public:
MainWindow1(QWidget* parent = nullptr)
: QMainWindow(parent)
{
mdiArea = new QMdiArea(this);
setCentralWidget(mdiArea);
QPushButton* openWindowBtn = new QPushButton("Open QML Window", this);
connect(openWindowBtn, &QPushButton::clicked, this, &MainWindow1::openQmlWindow);
setMenuWidget(openWindowBtn);
}
private:
void openQmlWindow()
{
QmlWindow* window = new QmlWindow();
QMdiSubWindow* subWindow = mdiArea->addSubWindow(window);
window->show();
}
QMdiArea* mdiArea;
};
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
MainWindow1 mainWindow;
mainWindow.show();
return app.exec();
}
O crash ocorre quando tento invocar plusBtn() depois que a janela é adicionada à QMdiArea. Às vezes, o aplicativo trava com Segmentation Fault, e outras vezes recebo a mensagem: QMetaObject::invokeMethod:No such method QQuickPinchHandler::plusBtn()
O que eu tentei: Eu verifiquei se o QQuickWidget foi inicializado corretamente, e setSource(QUrl("qrc:/Map.qml")) carrega o arquivo QML sem problemas. O arquivo QML carrega corretamente, rootObject() não é nullptr, e o método plusBtn() existe no objeto QML. A falha ocorre somente quando o widget é adicionado ao QMdiArea — se eu invocar plusBtn() antes de adicioná-lo, ele funciona bem, mas uma vez que o widget está dentro do QMdiArea, chamar plusBtn() às vezes causa uma falha de segmentação.
SaÃda de depuração antes da falha:
auto rootObj = ui->quickWidget->rootObject();
if (!rootObj) {
qWarning() << "Root object is null!";
return;
}
qDebug() << "Root object type:" << rootObj->metaObject()->className();
for (int i = 0; i < rootObj->metaObject()->methodCount(); ++i) {
qDebug() << "Method:" << rootObj->metaObject()->method(i).methodSignature();
}
SaÃda:
Root object type: Map_QMLTYPE_0
Method: "plusBtn()"
Eu apreciaria qualquer informação sobre o porquê disso estar acontecendo e como posso resolver o problema.
Ao conectar seu sinal:
você está fazendo uma suposição arriscada de que o ponteiro para o objeto raiz obtido em seu
QmlWindow
construtor permanecerá válido até que o botão seja pressionado e seu lambda seja invocado. Aparentemente, esse não é o caso. Não encontrei nenhuma linha na documentação que o mencione explicitamente, masrootObject
chame "Retorna o item raiz da visualização." e eu não confiaria que a "visualização" nunca seria alterada.Em vez disso, obtenha o
rootObject
ptr diretamente no seu retorno de chamada, especialmente considerando que você o mantémQQuickWidget*
como seu campo de classe de qualquer maneira: