用 C 创建自定义元素
QML 带有丰富的视觉元素。仅使用 QML,我们可以使用这些元素构建复杂的应用程序。此外,基于标准项目(如矩形,按钮,图像等)构建自己的元素非常容易。此外,我们可以使用像 Canvas 这样的项目来构建具有自定义绘画的元素。我们似乎只能在 QML 中构建各种应用程序,而不会触及 C++的功能。它实际上是正确的,但有时我们仍希望使我们的应用程序更快,或者我们希望用 Qt 的功能扩展它,或者增加一些 QML 中没有的机会。当然在 QML 中也存在这种可能性。基本上 QtQuick 使用 Scene Graph 将其内容绘制成基于的高性能渲染引擎 OpenGL 。要实现我们自己的可视元素,我们可以使用两种方式:
- 传统的 Qt 方式使用 QPainter ( QQuickPaintedItem )。
- 使用 QQuickItem 和 OpenGL 功能的常见 QML 方式。
有可能第一种方法似乎更容易,但值得考虑的是它也比第一种方法慢,因为 QtQuick 在表面上绘制项目的内容然后将其插入到场景图中,因此渲染是两步操作。因此,直接使用场景图 API 总是要快得多。
为了探索两种方法,让我们创建我们自己的元素,这在 QML 中肯定不存在,例如三角形。
类声明
class QQuickCustomItem : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
public:
QQuickCustomItem(QQuickItem *parent = Q_NULLPTR);
protected:
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData);
QColor color() const;
void setColor(const QColor &color);
private:
QColor m_color;
bool m_needUpdate;
signals:
void colorChanged();
};
我们添加 Q_OBJECT 宏来处理信号。我们还添加了自定义属性来指定 Rectangle 的颜色。为了使它工作,我们需要的是重新实现虚函数 QQuiclItem :: updatePaintNode()
。
类实现
首先,我们定义一个构造函数。
QQuickCustomItem::QQuickCustomItem(QQuickItem *parent) :
QQuickItem(parent),
m_color(Qt::red),
m_needUpdate(true)
{
setFlag(QQuickItem::ItemHasContents);
}
请注意, setFlag()
函数调用是必需的,否则你的对象将不会添加到场景图中。接下来,我们定义一个痛苦的函数。
QSGNode *QQuickCustomItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData)
{
Q_UNUSED(updatePaintNodeData)
QSGGeometryNode *root = static_cast<QSGGeometryNode *>(oldNode);
if(!root) {
root = new QSGGeometryNode;
QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 3);
geometry->setDrawingMode(GL_TRIANGLE_FAN);
geometry->vertexDataAsPoint2D()[0].set(width() / 2, 0);
geometry->vertexDataAsPoint2D()[1].set(width(), height());
geometry->vertexDataAsPoint2D()[2].set(0, height());
root->setGeometry(geometry);
root->setFlag(QSGNode::OwnsGeometry);
root->setFlag(QSGNode::OwnsMaterial);
}
if(m_needUpdate) {
QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
material->setColor(m_color);
root->setMaterial(material);
m_needUpdate = false;
}
return root;
}
在第一次调用函数时,我们的节点尚未创建,因此 oldNode
将为 NULL。因此,我们创建节点并为其指定几何和材质。这里我们使用 GL_TRIANGLE_FAN 为我们的几何体绘制实心矩形。这一点与 OpenGL 中的相同。例如,要绘制三角形框架,我们可以将代码更改为:
geometry->setDrawingMode(GL_LINE_LOOP);
geometry->setLineWidth(5);
你可以参考 OpenGL 手册来检查其他形状。所以,剩下的就是为我们的属性定义 setter / getter:
QColor QQuickCustomItem::color() const
{
return m_color;
}
void QQuickCustomItem::setColor(const QColor &color)
{
if(m_color != color) {
m_color = color;
m_needUpdate = true;
update();
colorChanged();
}
}
现在只有一个小细节可以使它工作。我们需要通知 QtQuick 新项目。例如,你可以将此代码添加到 main.cpp:
qmlRegisterType<QQuickCustomItem>("stackoverflow.qml", 1, 0, "Triangle");
这是我们的 QML 测试文件:
import QtQuick 2.7
import QtQuick.Window 2.0
import stackoverflow.qml 1.0
Window {
width: 800
height: 800
visible: true
Rectangle {
width: 200
height: 200
anchors.centerIn: parent
color: "lightgrey"
Triangle {
id: rect
width: 200
height: 200
transformOrigin: Item.Top
color: "green"
onColorChanged: console.log("color was changed");
PropertyAnimation on rotation {
from: 0
to: 360
duration: 5000
loops: Animation.Infinite
}
}
}
Timer {
interval: 1000
repeat: true
running: true
onTriggered: rect.color = Qt.rgba(Math.random(),Math.random(),Math.random(),1);
}
}
如你所见,我们的项目与所有其他 QML 项目一样。现在让我们使用 QPainter 创建相同的项目 :
我们所需要的就是更换
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData);
同
void paint(QPainter *painter);
并且,cource 从 QQuickPaintedItem
而不是 QQuickItem
继承我们的类。这是我们的绘画功能:
void QQuickCustomItem::paint(QPainter *painter)
{
QPainterPath path;
path.moveTo(width() / 2, 0);
path.lineTo(width(), height());
path.lineTo(0, height());
path.lineTo(width() / 2, 0);
painter->fillPath(path, m_color);
}
其他一切都保持不变。