您的当前位置:首页正文

将Qt Widgets嵌入到QML界面中的一种示范

2024-11-11 来源:个人技术集锦

关于有小伙伴遇到说是嵌入之后失去焦点等的疑问,我这里在文章尾部做统一回复。

 

*QML离屏渲染Qt Widgets

使用Qml中的控件QQuickPaintedItem来对Qt Widgets进行渲染以达到Wdigets可以很自然的显示在QML界面中.

先简单的讲述一下思路:

 

就是用QQuickPaintedItem 来将QWidget样子显示到QML页面中,并且把QQuickPaintedItem获得的事件合理的转发给QWidget让QWidget能处理对应的消息。类似于QQuickPaintedItem在QML页面中代理了QWidget

 

接下来直入主题,首先我们为这个例子建立一个类QmlOSRExpMainWindow:

.h

#ifndef QMLOSREXPMAINWINDOW_H
#define QMLOSREXPMAINWINDOW_H

#include <QQuickWindow>
#include <QQmlComponent>

class QmlOSRExpMainWindow : public QQuickWindow
{
public:
    QmlOSRExpMainWindow(QWindow *parent = 0);
    ~QmlOSRExpMainWindow();

    void load(QUrl src);
    QQuickItem* rootItem();

private:
    static QQmlEngine* gEngine;
    static int gWindowCount;
    QQmlComponent* mComponent;
    QQuickItem* mRootItem;

    static void checkInit();
    static void checkUnInit();
};

#endif // QMLOSREXPMAINWINDOW_H

.cpp


#include "QmlOSRExpMainWindow.h"
#include <QQmlEngine>
#include <QQmlComponent>
#include <QQmlContext>
#include <QWindow>
#include <QDebug>
#include <QQuickItem>

QQmlEngine* QmlOSRExpMainWindow::gEngine = 0;
int QmlOSRExpMainWindow::gWindowCount = 0;

QmlOSRExpMainWindow::QmlOSRExpMainWindow(QWindow *parent)
    :QQuickWindow(parent)
    ,mComponent(0)
    ,mRootItem(0)
{
    checkInit();

    mComponent = new QQmlComponent(gEngine);
}

QmlOSRExpMainWindow::~QmlOSRExpMainWindow()
{
    checkUnInit();
}

void QmlOSRExpMainWindow::load(QUrl src)
{
    if(mRootItem)
    {
        qDebug() << "release root item:" << mRootItem;
        delete mRootItem;
        mRootItem = NULL;
    }

    mComponent->loadUrl(src);
    if(mComponent->isReady()) //注意,这里可能没准备好,需要巡回检测状态通过才能继续,本例就不提供这个操作了。
    {
        QObject* root = mComponent->create();


        QQuickItem* rootItem = qobject_cast<QQuickItem*>(root);
        if(rootItem)
        {
            rootItem->setParentItem(this->contentItem());
            mRootItem = rootItem;
        }
        else
        {
            qDebug() << "load error: dosen't suport " << root;
        }

    }
    else
    {
        qDebug() << "QmlComponent ERROR:" << mComponent->errorString();
    }
}

QQuickItem *QmlOSRExpMainWindow::rootItem()
{
    return mRootItem;
}

void QmlOSRExpMainWindow::checkInit()
{
    //_internal
    if(gWindowCount++ == 0)
    {
        qDebug() << "init qml engine";
        gEngine = new QQmlEngine;
    }
    //
}

void QmlOSRExpMainWindow::checkUnInit()
{
    //_internal
    if(--gWindowCount == 0)
    {
        qDebug() << "release qml engie";
        delete gEngine;
        gEngine = NULL;
    }
    //
}

以上代码实现了QmlOSRExpMainWindow作为我们本例子的主窗口.

接下来我们继承QQuickPaintedItem来实现一个可以在QML渲染Widgets的控件WidgetOSRItem:

.h

#ifndef WIDGETOSRITEM_H
#define WIDGETOSRITEM_H

#include <QQuickPaintedItem>

class WidgetOSRItem : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QVariant osrWidget WRITE setOSRWidget)
public:
    WidgetOSRItem();

    void setOSRWidget(QVariant w); //* 加入要渲染的widget,不维护这个weiget的生命周期, var -->> QWidget*

    virtual void paint(QPainter *painter) override; //重载paint函数

protected:
    virtual bool eventFilter(QObject* obj, QEvent* e) override;


    //当item改变大小位置的时候在这里同步改变OSRWidget的位置和大小
    virtual void geometryChanged(const QRectF & newGeometry, const QRectF & oldGeometry) override;

    virtual void	hoverEnterEvent(QHoverEvent * e) override;
    virtual void	hoverLeaveEvent(QHoverEvent * e) override;
    virtual void	hoverMoveEvent(QHoverEvent * e) override;
    virtual bool    event(QEvent *e) override; //*只有通过这个函数处理的才会转成对应的Event,在这里过滤消息

private:
    QWidget* mOSRWidget;
};

#endif // WIDGETOSRITEM_H

.cpp

#include "WidgetOSRItem.h"
#include <QWidget>
#include <QPainter>
#include <QDebug>

WidgetOSRItem::WidgetOSRItem()
    :mOSRWidget(0)
{
    this->setAcceptHoverEvents(true);
    this->setAcceptedMouseButtons(Qt::AllButtons);
    setFlag(ItemAcceptsInputMethod, true);
    setFlag(ItemIsFocusScope, true);
    setFlag(ItemHasContents, true);
}

void WidgetOSRItem::setOSRWidget(QVariant w)
{
    mOSRWidget = w.value<QWidget*>();
    if(mOSRWidget)
    {
        mOSRWidget->installEventFilter(this);
        this->update();
    }
}

void WidgetOSRItem::paint(QPainter *painter)
{
    painter->save();
    if(mOSRWidget == NULL)
    {
        painter->drawText(this->boundingRect(), Qt::AlignCenter, "painted item");
    }
    else
    {
        mOSRWidget->render(painter);
    }

    painter->restore();
}

bool WidgetOSRItem::eventFilter(QObject *obj, QEvent *e)
{
    bool res = QQuickPaintedItem::eventFilter(obj, e);
    if(obj == mOSRWidget)
    {
        switch(e->type())
        {
        case QEvent::Paint: //当OsrWidget paint的时候也触发自己paint
        {
            this->update();
        }
            break;
        }
    }

    return res;
}

void WidgetOSRItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{
    QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry);

    if(mOSRWidget)
    {
        mOSRWidget->setGeometry(newGeometry.toRect());
    }
}

void WidgetOSRItem::hoverEnterEvent(QHoverEvent *e)
{
   if(!mOSRWidget)
       return;
   QEnterEvent enterEvent(e->posF(), mapToScene(e->posF()), QCursor::pos());//QEnterEvent(const QPointF & localPos, const QPointF & windowPos, const QPointF & screenPos)
   qApp->sendEvent(mOSRWidget, &enterEvent);
}

void WidgetOSRItem::hoverLeaveEvent(QHoverEvent *e)
{
    if(!mOSRWidget)
        return;
    qApp->sendEvent(mOSRWidget, e);
}

void WidgetOSRItem::hoverMoveEvent(QHoverEvent *e)
{
    if(!mOSRWidget)
        return;
    QMouseEvent me(QEvent::MouseMove, e->posF(), mapToScene(e->posF()), QCursor::pos(), Qt::NoButton, Qt::NoButton, 0);
    qApp->sendEvent(mOSRWidget, &me);
}

bool WidgetOSRItem::event(QEvent *e)
{
    if(!mOSRWidget)
    {
        e->ignore();
        return false;
    }

    switch(e->type())
    {
    case QEvent::HoverEnter:
    case QEvent::HoverLeave:
    case QEvent::HoverMove:
    {
        return QQuickPaintedItem::event(e);
    }
        break;
    default:
        return qApp->sendEvent(mOSRWidget, e);
    }
}

以上代码实现了可以在QML中渲染Widget的控件WidgetOSRItem,只要注册到QML中就可以使用这个控件.

主要的部分弄好了,接下来让我们完成这个例子.

首先写一个qml界面文件WidgetOSRExp.qml,这个qml界面左边是一个TextEidt,右边是用来渲染Widgets的WidgetOSRItem控件,
我们可以通过设置osrItem_OSRWidget的值将要渲染的widget设置给WidgetOSRItem控件:

.qml

import QtQuick 2.3
import QtQuick.Window 2.2

import Diy.WidgetOSRItem 1.0

Item
{
    id: osrRoot
    anchors.fill: parent
    property alias osrItem_OSRWidget : osrItem.osrWidget

    Text
    {
        id: expName
        text: "WidgetOSRExp"
        anchors.left: parent.left
        anchors.top: parent.top
        anchors.right: parent.right
        height: 18
        font.pointSize: 14
        verticalAlignment: Text.AlignVCenter
        horizontalAlignment: Text.AlignHCenter
    }

    Rectangle
    {
        id: qmlTitle
        Text
        {
            text: "Qml TextEdit"
            anchors.fill: parent
            font.pointSize: 14
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
        }

        anchors.left: parent.left
        anchors.top: expName.bottom
        width: parent.width/2
        height: 18
        color: "lightblue"
    }

    Rectangle
    {
        id: qtlTitle
        Text
        {
            text: "Qt QWebView"
            anchors.fill: parent
            font.pointSize: 14
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
        }

        anchors.left: qmlTitle.right
        anchors.top: expName.bottom
        width: parent.width/2
        height: 18
        color: "darkred"
    }

    TextEdit
    {
        id: qmlTextEdit
        anchors.top: qmlTitle.bottom
        anchors.left: qmlTitle.left
        anchors.right: qmlTitle.right
        anchors.bottom: parent.bottom
        text:"hello I'm Qml Text Editor !"
    }


    WidgetOSRItem //OSR ITEM
    {
        id: osrItem
        anchors.top: qtlTitle.bottom
        anchors.left: qtlTitle.left
        anchors.right: qtlTitle.right
        anchors.bottom: parent.bottom

        MouseArea//控制osrItem的焦点
        {
            anchors.fill: parent;
            propagateComposedEvents: true

            onPressed:
            {
                mouse.accepted = false
                parent.focus = true
            }
            onReleased: mouse.accepted = false
            onMouseXChanged: mouse.accepted = false
            onMouseYChanged: mouse.accepted = false
        }
    }
}

在这个文件中import Diy.WidgetOSRItem 1.0 这句话的Diy.WidgetOSRItem 1.0是我注册进去的WidgetOSRItem控件的引用.

控件和qml都准备好了,让我们开始吧:

实现main函数:

main.cpp

#include "mainwindow.h"
#include <QApplication>
#include <QWebView>
#include "QmlOSRExpMainWindow.h"
#include "WidgetOSRItem.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL, true);

    //register diy qml tools
    qmlRegisterType<WidgetOSRItem>("Diy.WidgetOSRItem",1,0, "WidgetOSRItem");

    QmlOSRExpMainWindow w;
    w.show();
    w.load(QUrl("qrc:/WidgetOSRExp.qml"));


    //新建一个QWebView放入qml osr item中
    QWebView osrWebView;
    osrWebView.setUrl(QUrl("http://www.baidu.com"));
    w.rootItem()->setProperty("osrItem_OSRWidget", QVariant::fromValue(&osrWebView));

    int res =  a.exec();
    return res;
}

这个main函数中我加载了qml界面,并为右边的渲染控件设置了一个QWebView进去.
运行结果(图片太大进行了缩放):

让我们试试渲染一个QListView,运行结果:

至此本例结束,这个方法各位可以尝试一下.

 

 

 

 

关于有小伙伴说是嵌入之后失去焦点等的疑问:

答:

该方案的核心思路是将widgets渲染的画面由qml显示,所以实际上QML窗口的event对于widgets来讲是没法收到的,这时候我们要做转换,把qml的event转发给widget。只要做了这一步嵌入的widget就活了。

我这里提供了一个示例项目。大致的给各位演示如何操作。

(环境:WIN10,VS2010,QT 5.5.1)

这是操作QListView和带有按钮的窗口的效果图:

图1.QListView效果图,可输入和拖动滚轮

 

 

 

图2.可进行按钮的点击

 

这是示例工程的分享链接:

 

 

Top