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对应的节点的子节点了。
- 自定义的类需要继承自QQuickItem。
- 需要自己实现QQuickItem的虚函数QQuickItem::updatePaintNode
- 需要设置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();
}
例子中,还有一些点需要继续探索:
- 重新实现的继承自QQuickItem类的void keyPressEvent函数并没有被触发,需要进一步看资料。
- 在main.qml中调用selfDefined.changeColor()时,会报错:TypeError: Cannot call method 'changeColor' of null,为什么找不到对象selfDefined呢,可能是将这个类放在qml中使用时有些地方需要注意。最好的方式是,看一下qt源码中的QQuickRectangle是如何实现的。