c++qtqmlqquickwidget

Why using QQuickWindow::grabWindow() causes a window to turn into an image?


I have a QQuickWidget and want to grab a screenshot using QQuickWindow::grabWindow(). However when I do that the QQuickWindow becomes an image and is not responsive.

Below is a minimal reproducible code: The bug is reproducible in Qt5.13 to Qt5.15.1 in release mode (for some reason Qt throws an assert in debug)

//TestWidget.pro

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets quickwidgets

CONFIG += c++11

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    mainwindow.h \
    windowgrabber.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

DISTFILES += \
    Main.qml

RESOURCES += \
    qml.qrc

//main.cpp

#include <QApplication>
#include <QQuickWidget>
#include <QQmlContext>
#include "windowgrabber.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QQuickWidget *quickWidget = new QQuickWidget;
    quickWidget->rootContext()->setContextProperty("windowGrabber", new WindowGrabber(quickWidget));
    quickWidget->setSource(QUrl("qrc:/Main.qml"));
    quickWidget->show();

    return a.exec();
}

//Main.qml

import QtQuick 2.0
import QtQuick.Controls 2.0

Page {
    Button {
        id: button
        text: "grab window"
        onClicked: windowGrabber.grabWindow(button)
    }
}

//WindowGrabber.h

#ifndef WINDOWGRABBER_H
#define WINDOWGRABBER_H

#include <QObject>
#include <QQuickItem>
#include <QQuickWindow>

class WindowGrabber : public QObject
{
    Q_OBJECT
public:
    WindowGrabber(QObject *parent = nullptr) : QObject(parent) {}
    Q_INVOKABLE static void grabWindow(QQuickItem *item) {
        item->window()->grabWindow();
    }
};

#endif // WINDOWGRABBER_H

The code creates a QQuickWidget with source set to Main.qml. I want to take a screenshot when the button inside the qml is clicked. But after clicking the button the QQuickWindow inside the quickwidget becomes an image and button also becomes an image. I have tested with QWidget::createWindowContainer and it works, but the best solution would be to use a QQuickWidget. Anyone have any clues to why this might happen?


Solution

  • QQuickWidget uses a non-visible QQuickWindow to render the items, and that rendering is rendered again in the QWidget, so when trying to save the image it interferes, causing the lag as I have already pointed out in this post.

    A possible solution is to grab directly from the QQuickWidget:

    #ifndef WINDOWGRABBER_H
    #define WINDOWGRABBER_H
    
    #include <QObject>
    #include <QQuickWidget>
    
    class WindowGrabber : public QObject
    {
        Q_OBJECT
    public:
        WindowGrabber(QQuickWidget *widget): m_widget(widget)  {}
        Q_INVOKABLE void grabWindow() {
            if(m_widget){
                QPixmap img = m_widget->grab();
                qDebug() << img;
            }
        }
    private:
        QPointer<QQuickWidget> m_widget;
    };
    
    
    #endif // WINDOWGRABBER_H
    
    #include <QApplication>
    #include <QQuickWidget>
    #include <QQmlContext>
    #include "windowgrabber.h"
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        QQuickWidget quickWidget;
        WindowGrabber grabber(&quickWidget);
        quickWidget.rootContext()->setContextProperty("windowGrabber", &grabber);
        quickWidget.setSource(QUrl("qrc:/main.qml"));
        quickWidget.show();
    
        return a.exec();
    }
    
    Button {
        id: button
        text: "grab window"
        onClicked: windowGrabber.grabWindow()
    }