38

I am using Qt5 beta and trying to embed a QWidget-based object into QML. The goal is to use QML as much as possible, and only use QWidget objects where QML does not do what I need. I found a link explaining how to do this for Qt4.7, but I have not found any information explaining how to do this in Qt5.

http://doc.qt.digia.com/4.7/declarative-cppextensions-qwidgets.html

The same example is also available in the Qt5 examples folder under:

examples\qtquick1\declarative\cppextensions\qwidgets

Unfortunately, this example uses QtQuick 1, rather than QtQuick 2, and I would like to use the new features of Qt5. I actually want to embed a qwt widget, but as a first step I would be happy to embed any simple QWidget-based object.

Can anybody help me get the example working under Qt5 / QtQuick 2 ?

eatyourgreens
  • 1,053
  • 1
  • 12
  • 16
  • 2
    Some relevant news regarding this: [QtQuickWidget](https://qt.gitorious.org/qt/qtdeclarative/source/b02bed1caae6966925b9efb04e1db79c3e9ef687:src/quickwidgets/qquickwidget.cpp#L169) solves the reverse of this problem; that is, embedding Qt Quick items into widgets. It will be available in Qt 5.3. – Mitch Mar 04 '14 at 14:38

5 Answers5

31

Qt Quick 2 uses a scene graph for efficient rendering on the GPU. Unfortunately this makes it impossible to embed classic widgets into the scene. The old approach to embed such widgets with the help of QGraphicsProxyWidget works only with Qt Quick 1, because internally it uses a QGraphicsView for all the heavy lifting and QGraphicsProxyWidget is meant to be used with it.

As of now there are no plans to enable embedding classic QWidgets into the scene graph I know of. I think this is rather unlikely to change, because the concepts of QPainter, the painting framework used for the classic widgets, and the new scene graph doesn't play well with each other.

There some efforts to develop new widgets sets specifically tailored for the needs of QML, but none of them are as powerful and mature as the classic widgets. The most prominent ones are the QML Quick Controls, bundled with Qt since version 5.1.

If you really depend on QWT my advice would be to stick with Qt Quick 1.1 for now. It's still bundled with Qt 5, probably for cases like yours. That way you won't take advantage of the new scene graph, though.

sebasgo
  • 3,845
  • 23
  • 28
  • Thank you for your very clear explanation. My project is still new enough that I am not totally committed to QWT. – eatyourgreens Oct 24 '12 at 08:36
  • 4
    Update: The pure QML widgets aka components has matured a lot since this answer was posted. – Mr. Developerdude Jan 29 '15 at 03:13
  • 2
    -1: It is completely false that it is "impossible" to embed regular QWidgets into a Qt Quick 2 scene. It is not only possible, it also works pretty well. I do it in my application and the performance is very good. See [this](https://stackoverflow.com/a/13631246/4213607) and [this](https://stackoverflow.com/a/42921163/4213607) answer and this [repository](https://github.com/mosolovsa/qmlplot) for an example. – fkorsa Jun 22 '18 at 08:11
9

You can embed QWidget to QML by using QQuickPaintedItem class: http://doc.qt.io/qt-5/qquickpainteditem.html

Qt5 has an example: http://doc.qt.io/qt-5/qtquick-customitems-painteditem-example.html

You should implement an inherent of QQuickPaintedItem with private widget attribute, that you want to embed. Provide paint method, that just render the QtWidget and provide mouse and other event transmitting from inherit of QQuickPaintedItem to embed QtWidget.

There's also QSG (Qt scene graph API), but my experience with that thing wasn't smooth. I believe the clue in multithreading (performing rendering in the different thread (not the Qt GUI thread one, however on Windows that's not true and all is done in main GUI thread).

I've implemented embedding of QCustomPlot, here's link: github.com/mosolovsa/qmlplot

Mosolov Sergey
  • 514
  • 6
  • 13
8

What could be done is to render the widget to an image and upload as texture.For interaction someone needs to forward events like mouseClick or keyPressed from the sceneGraph, translate to widget coordinates, pass on, render and upload texture again. Just an idea :)

Sebastian
  • 81
  • 2
8

Further to Julien's answer - a simple way to achieve this is to use QQuickWidget to display the QML scene, and then add a regular QWidget as a child of the QQuickWidget. You can also add a simple intermediate QObject to anchor the QWidget to an item in the scene.

E.g.:

In main.qml:

Item {

    ... // layouts, extra items, what have you

        Item
        {
            objectName: "layoutItem"
            anchors.fill: parent
        }

    ... // more layouts, extra items, etc.
}

widgetanchor.h:

class WidgetAnchor: public QObject
{
    ptr<QWidget> _pWidget;
    QPointer<QQuickItem> _pQuickItem;
public:
    WidgetAnchor(QWidget* pWidget, QQuickItem* pItem)
        : QObject(pWidget), _pWidget(pWidget), _pQuickItem(pItem)
    {
        connect(_pQuickItem, &QQuickItem::xChanged, this, &WidgetAnchor::updateGeometry);
        connect(_pQuickItem, &QQuickItem::yChanged, this, &WidgetAnchor::updateGeometry);
        connect(_pQuickItem, &QQuickItem::widthChanged, this, &WidgetAnchor::updateGeometry);
        connect(_pQuickItem, &QQuickItem::heightChanged, this, &WidgetAnchor::updateGeometry);
        updateGeometry();
    }
private:
    void updateGeometry()
    {
        if (_pQuickItem)
        {
            QRectF r = _pQuickItem->mapRectToItem(0, QRectF(_pQuickItem->x(), _pQuickItem->y(), _pQuickItem->width(), _pQuickItem->height()));
            _pWidget->setGeometry(r.toRect());
        }
    }
};

In main.cpp:

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

    auto pqw = new QQuickWidget;
    pqw->setSource(QUrl::fromLocalFile("main.qml"));
    pqw->setResizeMode(QQuickWidget::SizeRootObjectToView);
    pqw->setAttribute(Qt::WA_DeleteOnClose);
    auto pOwt = new MyWidget(pqw);
    if (auto pOverlayItem = pqw->rootObject()->findChild<QQuickItem*>("overlayItem"))
        new WidgetAnchor(pOwt, pOverlayItem);
    pqw->show();

    return app.exec();
}

The documentation states that using QQuickWidget has advantages over QQuickView and QWidget::createWindowContainer, such as no restrictions on stacking order, but has a 'minor performance hit'.

Hope that helps.

NickD2039
  • 170
  • 1
  • 7
  • Hey thanks! I like your approach. Would it be possible to do it when the app uses QQmlApplicationEngine instead of QQuickWidget? – laurapons Dec 01 '19 at 16:00
  • need not `mapRectToItem` and ptr is a smart pointer. Overall, this's a good method. – Crawl.W Mar 30 '20 at 01:17
  • 1
    works great. For Qt6 you need to add `QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi);` [qt doc](https://doc.qt.io/qt-6/quick-changes-qt6.html#changes-to-qquickwidget) – Alexander A. Mar 11 '22 at 09:38
7

The recommended approach is to stay with a QWidget based application and embed the QML parts using QWidget::createWindowContainer.

Julian
  • 1,522
  • 11
  • 26
  • 2
    -1: Even if I agree with you, this does not answer the question. The question is about embedding a QWidget inside a QML scene. While it is true that it is generally better to keep the application QWidget-based, you may still want to embed a QWidget inside your QML view (which is exactly the case in my application, for example). – fkorsa Jun 22 '18 at 08:18