Qt5 QtQuick系列----QtQuick的Secne Graph剖析(2)--自定义QML类型 (继承QQuickItem)

                                                                             “当下即永恒”  --- 佚名


Qt用户可以方便地使用QML中的Rectangle等基本类型,但是当不够用时,或,需要开发更高级的界面时,可以自己定义QML类型。

自定义QML类型需要继承自QQuickItem类,首先,需要了解这个类:http://doc.qt.io/qt-5/qquickitem.html  官方文档讲得很清楚:

The QQuickItem class provides the most basic of all visual items in Qt Quick.

All visual items in Qt Quick inherit from QQuickItem. Although a QQuickItem instance has no visual appearance, it defines all the attributes that are common across visual items, such as x and y position, width and height, anchoring and key handling support.

You can subclass QQuickItem to provide your own custom visual item that inherits these features.

QML中的Item元素与C++中的QQuickItem类对应。


基本上所有的基本qml类型都可以通过用户继承QQuickItem来自己实现吧。实现自定义qml类型,需要注意以下几点:

 函数的作用:允许你自己在函数内部定义QSGNode结构,允许你自己定义一个subtree,最后返回这个subtree的根节点。 在显示qml到屏幕的流程中(参考我的博文:https://blog.csdn.net/qq_35865125/article/details/86485008 ),qml最终会被转换成一个树,树的节点是QSGNode类型的,对于已经存在的qt给我们定义的qml基本类型,例如Rectangle,它们应该也是通过调用自己的updatePainNode来实现将自己转换成QSGNode的,只不过是qt公司的人帮我们写好了,这里我们自己定义节点的话,就需要自己实现这个函数了。另外,这个函数返回的QSGNode*是会被自动加入到一个更大的树结构中的。例如,你自己实现了一个类型myQMLType,然后,你在文件中将其放在一个Rectangle内部,于是乎你自己定义的这个类型对应的节点就是Rectangle对应的节点的子节点了。

  1. 自定义的类需要继承自QQuickItem
     
  2. 需要自己实现QQuickItem的虚函数QQuickItem::updatePaintNode

  3. 需要设置QQuickItem::ItemHasContents标记。

我一般在在构造函数中进行设置:setFlags(QQuickItem::ItemHasContents); 只有设置了这个标记,你自己定义的updatePainNode才可以被自动执行。

从上面两处截图可以知道,设置了标识之后,QQuickItem::update才能够被调用,这个函数会最终导致你自己实现的updatePainNode函数被调用。
4) 需要调用qmlRegisterType函数来将你自己定义的c++类型注册到QML环境中,并在qml文件中机型import才能在qml文件中直接使用。
e.g:在main函数中: qmlRegisterType<SelfDefinedQMLType>("SelfDefinedQMLType", 1, 0, "SelfDefinedQMLType");

     在qml文件中:  import  SelfDefinedQMLType  1.0
 

5)要弄清楚资源清理方式:

例如,在下面我给出的例子中,如果在析构函数中调用delete QSGSimNode,会报错,应该是qt会自动处理这个资源,无需手动。

 

5)要认识到QQuickItem类中有很多函数的,例如QQuickItem::keyPressEvent函数,可以在你的子类中重新实现这个函数,处理按键消息。具体可以自己细看官方文档。

 

6) 单独的QquickItem并不能单独被显示,需要借助QquickWindow来显示。--一个qml文件中包含很多个Rectangle,text等基本组件,它们对应的C++类都继承自quick的最基本类:QQuickItem,这些个qml文件中的控件被组织成一个树结构,树的每个节点的类型都可以看成是QQuickItem,然后,这个树由 QquickWindow负责显示出来,主要是  通过调用底层opengl渲染出来(渲染的过程一般是一个单独的线程。)::

                       

 

请看下面的一个我的例子:(或许官网上还有其他更好的例子)

网盘:
自定义的类,头文件:

#ifndef SELFDEFINEDQMLTYPE_H
#define SELFDEFINEDQMLTYPE_H

#include <QSGSimpleRectNode>
#include <QtQuick/QQuickItem>
class SelfDefinedQMLType: public QQuickItem
{
    Q_OBJECT
public:
    SelfDefinedQMLType();
    ~SelfDefinedQMLType();

    Q_INVOKABLE void changeColor();

protected:
    QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *);
    void keyPressEvent(QKeyEvent *event);
private:
    QSGSimpleRectNode* QSGSimNode;
};

#endif // SELFDEFINEDQMLTYPE_H

自定义的类,cpp:

#include "SelfDefinedQMLType.h"

SelfDefinedQMLType::SelfDefinedQMLType()
{
    setFlags(QQuickItem::ItemHasContents);
    QSGSimNode = NULL;
    //setFocus(true);
    qDebug()<<"SelfDefinedQMLType::SelfDefinedQMLType() was called!";

}
SelfDefinedQMLType::~SelfDefinedQMLType()
{
    qDebug() << "SelfDefinedQMLType::~SelfDefinedQMLType start";
    if(QSGSimNode)
    {
        //Must comment the following, otherwise, there will be an error!
        //seems that the qt can handle resource itself.

        //delete QSGSimNode;
    }
    qDebug() << "SelfDefinedQMLType::~SelfDefinedQMLType end";
}
void SelfDefinedQMLType::changeColor()
{
    if (!QSGSimNode) {
        if( QColor(255, 0, 0, 127) == QSGSimNode->color() )
            QSGSimNode->setColor(QColor(255, 0, 0, 127));
        else
            QSGSimNode->setColor(QColor(0, 255, 0, 127));
    }
}
QSGNode *SelfDefinedQMLType::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
{
    //define a rectangle:

    QSGSimNode = static_cast<QSGSimpleRectNode *>(node);
    if (!QSGSimNode) {
        QSGSimNode = new QSGSimpleRectNode();
        QColor myColor = QColor(255, 0, 0, 127);
        QSGSimNode->setColor(myColor);
        QSGSimNode->setRect(10,10,400,400);
    }

    return QSGSimNode;

}

//why: this func can not be activated, even add "setFocus(true);" in SelfDefinedQMLType::SelfDefinedQMLType()
void SelfDefinedQMLType::keyPressEvent(QKeyEvent *event)
{
    if(Qt::Key_Left == event->key())
        changeColor();
}

 main.qml

import QtQuick 2.11
import QtQuick.Window 2.11
import SelfDefinedQMLType 1.0


import QtQuick 2.4
import QtQuick.Layouts 1.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4

Window {
    id:windowTop
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Rectangle{
        id:rect1
        x:0
        y:0
        visible: true
        anchors.fill: parent
        color: "steelblue"
        Keys.enabled: true
        focus: true

        SelfDefinedQMLType{
            id:selfDefined
        }

        Keys.onPressed: {
            switch(event.key)
            {
            case Qt.Key_Left:
                console.log("Qt.Key_Left was pressed!!!")
                //don't know why,the following repor error, during execution.

                selfDefined.changeColor()//Error:TypeError: Cannot call method 'changeColor' of null

                break;
            }
        }
    }

}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "SelfDefinedQMLType.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    qmlRegisterType<SelfDefinedQMLType>("SelfDefinedQMLType", 1, 0, "SelfDefinedQMLType");

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

例子中,还有一些点需要继续探索:

  1. 重新实现的继承自QQuickItem类的void keyPressEvent函数并没有被触发,需要进一步看资料。
  2. 在main.qml中调用selfDefined.changeColor()时,会报错:TypeError: Cannot call method 'changeColor' of null,为什么找不到对象selfDefined呢,可能是将这个类放在qml中使用时有些地方需要注意。最好的方式是,看一下qt源码中的QQuickRectangle是如何实现的。
posted @ 2019-01-15 22:37  JadeCicada  阅读(1225)  评论(0编辑  收藏  举报