Tuesday, July 5, 2011

How to display QWidget into QML

Recently in one of my project I required to embed QWidget derived class to QML scene. While its straight forward to embed QML scene to QWidget or QGraphicsScene.

It required small effort to embed QWidget derived class to QML scene. Still its not great effort and its also vary straigt forward.

All you need to do is, create new class derived from QDeclarativeItem. Create instance of required widget and add that widget to QGraphicsProxyWidget. Now we need to register this newly created class to QML using qmlRegisterType. Now we can use this class from QML which display our QWidget derived class.

Following is code that demonstrate the same. For demo I have created class which embed QLabel to QML scene.

Following code is my custom QDeclarativeItem derive class which will expose QLabel to QML scene.
#include <QDeclarativeItem>

class QLabel;

class QmlLabel : public QDeclarativeItem
{
    Q_OBJECT    
    Q_PROPERTY(QString text READ text WRITE setText)
public:
    explicit QmlLabel(QDeclarativeItem *parent = 0);
    ~QmlLabel();    

public slots:

    void setText(const QString& text);

    QString text() const;

private:

    QLabel* mLabel;
    QGraphicsProxyWidget* mProxy;

};

#include <QLabel>
#include <QGraphicsProxyWidget>

QmlLabel::QmlLabel(QDeclarativeItem *parent) :
    QDeclarativeItem(parent)
{
    mLabel = new QLabel(QString(""));
    mProxy = new QGraphicsProxyWidget(this);
    mProxy->setWidget(mLabel);
}

QmlLabel::~QmlLabel()
{
    delete mLabel;
}

void QmlLabel::setText(const QString& text)
{
    mLabel->setText(text);
}

QString QmlLabel::text() const
{
    return mLabel->text();
}

Following code demonstrate how to register above class to QML. In qmlRegisterType first argument is component uri, which is used for importing component to QML. second and third argument is version information and fourth argument is element name in QML, By using this name we can created QML element.

#include <QtDeclarative>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    qmlRegisterType<QmlLabel>("qmlLabel", 1, 0, "QmlLabel");
    QDeclarativeView viewer;
    viewer.setSource(QUrl("qml/hybrid/main.qml"));
    viewer.show();
    return app.exec();
}

Finally following is QML code. To be able to use our custom QML element we need to import component to QML using import statement.
import QtQuick 1.0
import qmlLabel 1.0

Rectangle {
    width: 360
    height: 360
    QmlLabel {         
        x:100; y:100
        text: "QML Label"
    }
}

Following is out put from above code.

14 comments:

  1. Thanks a ton!

    ReplyDelete
  2. Thanks.. It was really helpful....

    ReplyDelete
  3. Thanks, quite helpful.

    ReplyDelete
  4. Thank you, I'll try this later, it's exactly what I need :)

    ReplyDelete
  5. Thank you so much.
    It is the one I am ever looking for.
    It save my days.

    Many many thanks.

    ReplyDelete
  6. my qwidget draw lines with data points coming from a data source, I override paintEvent and used QPainter to drawLine. Most of the time it works fine but when there's an update on the other QML elements (or refresh), the lines either dissappear or disconnected.

    ReplyDelete
  7. Can you provide your source code, I will try for solution.

    ReplyDelete
  8. Thank you!

    By the way, with this qml code (C++ classes are same):

    Rectangle {
    width: 360
    height: 360
    QmlLabel {
    id: mylabel
    x:100; y:100
    text: "QML Label"
    }
    MouseArea {
    anchors.fill: parent
    onClicked: { mylabel.setText(mouseX + " " + mouseY); }
    }
    }

    just after start I see wery short label, only a left half of letter 'Q'.
    After first click I see label with width (gray field) enought to fit coordinates, but only with firt sybmol printed. After third click (especially at another place, string with coordinates must be changed) at last I see all string.


    Please, can you explain?
    What I should do to automatic repaint scene after all items were built?

    ReplyDelete
    Replies
    1. Make sure you give a width and height for QmlLabel in your qml file

      Delete
  9. I think you need to call update after set text, but let me try what you are doing and will update the post.

    ReplyDelete
  10. Also try this variant:

    #ifndef QMLLABEL_H
    #define QMLLABEL_H

    #include

    class QLabel;

    class QmlLabel : public QGraphicsProxyWidget
    {
    Q_OBJECT
    Q_PROPERTY(QString text READ text WRITE setText)
    public:
    explicit QmlLabel(QGraphicsProxyWidget* parent = 0);
    ~QmlLabel();

    public slots:

    void setText(const QString& text);

    QString text() const;

    private:
    QLabel* mLabel;
    };

    #endif // QMLLABEL_H


    #include "qmllabel.h"
    #include

    QmlLabel::QmlLabel(QGraphicsProxyWidget* parent)
    : QGraphicsProxyWidget(parent)
    {
    mLabel = new QLabel(QString(""));
    mLabel->setAttribute(Qt::WA_NoSystemBackground);
    setWidget(mLabel);
    }

    QmlLabel::~QmlLabel()
    {
    }

    void QmlLabel::setText(const QString& text)
    {
    mLabel->setText(text);
    }

    QString QmlLabel::text() const
    {
    return mLabel->text();
    }

    ReplyDelete
  11. I was wondering before trying that would it work for my complex custom widget(a spectrum visualizer).
    After trying, it worked perfectly .
    I would say it's simpler than other methods available.
    Thanks a lot! :)

    ReplyDelete
    Replies
    1. Thank you for your comment :), I am glad it helped you.

      Delete