最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - Crash when invoking QML function inside QMdiArea using QMetaObject::invokeMethod - Stack Overflow

programmeradmin5浏览0评论

Problem:

I have a QQuickWidget inside a QWidget, and I’m adding this widget to a QMdiArea. I am trying to call a QML function (plusBtn()) from C++ using QMetaObject::invokeMethod. However, my application crashes when I try to invoke the method. This crash happens only when the window is added to the QMdiArea. When I remove the widget from QMdiArea, everything works fine.

Here is my simplified code:

QML (Map.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++ (MainWindow Implementation):

#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();
}

The crash occurs when I try to invoke plusBtn() after the window is added to the QMdiArea. Sometimes the application crashes with Segmentation Fault, and other times I get the message: QMetaObject::invokeMethod: No such method QQuickPinchHandler::plusBtn()

What I tried: I checked that QQuickWidget is properly initialized, and setSource(QUrl("qrc:/Map.qml")) loads the QML file without issues. The QML file loads correctly, rootObject() is not nullptr, and the method plusBtn() exists in the QML object. The crash occurs only when the widget is added to QMdiArea—if I invoke plusBtn() before adding it, it works fine, but once the widget is inside QMdiArea, calling plusBtn() sometimes causes a segmentation fault.

Debugging Output Before the Crash:

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();
}

Output:

Root object type: Map_QMLTYPE_0  
Method: "plusBtn()"  

I would appreciate any insights on why this might be happening and how I can resolve it.

Problem:

I have a QQuickWidget inside a QWidget, and I’m adding this widget to a QMdiArea. I am trying to call a QML function (plusBtn()) from C++ using QMetaObject::invokeMethod. However, my application crashes when I try to invoke the method. This crash happens only when the window is added to the QMdiArea. When I remove the widget from QMdiArea, everything works fine.

Here is my simplified code:

QML (Map.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++ (MainWindow Implementation):

#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();
}

The crash occurs when I try to invoke plusBtn() after the window is added to the QMdiArea. Sometimes the application crashes with Segmentation Fault, and other times I get the message: QMetaObject::invokeMethod: No such method QQuickPinchHandler::plusBtn()

What I tried: I checked that QQuickWidget is properly initialized, and setSource(QUrl("qrc:/Map.qml")) loads the QML file without issues. The QML file loads correctly, rootObject() is not nullptr, and the method plusBtn() exists in the QML object. The crash occurs only when the widget is added to QMdiArea—if I invoke plusBtn() before adding it, it works fine, but once the widget is inside QMdiArea, calling plusBtn() sometimes causes a segmentation fault.

Debugging Output Before the Crash:

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();
}

Output:

Root object type: Map_QMLTYPE_0  
Method: "plusBtn()"  

I would appreciate any insights on why this might be happening and how I can resolve it.

Share Improve this question asked 12 hours ago i.troyeni.troyen 1011 silver badge8 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 2

When connecting your signal:

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");
});

you are making a risky assumption that the pointer to root object obtained in your QmlWindow constructor will stay valid until the button is pressed and your lambda is invoked. Apparently that's not the case. I didn't find any line in documentation that mentions it explicitly, but rootObject call "Returns the view's root item." and I wouldn't rely on the "view" to never be altered.

Instead obtain the rootObject ptr directly in your callback, especially considering that you keep QQuickWidget* as your class field anyway:

QPushButton* button = new QPushButton("Call QML Method");
connect(button, &QPushButton::pressed, this, [this]() {
    auto rootObj = quickWidget->rootObject();
    if (!rootObj) {
        qWarning() << "Root object is null!";
        return;
    }
    QMetaObject::invokeMethod(rootObj, "plusBtn");
});
发布评论

评论列表(0)

  1. 暂无评论