如何将自定义 GraphicsItem 集成到 QML 场景中?

How to integrate a custom GraphicsItem into a QML scene?

假设您在 C++ 中创建了以下自定义 QGraphicsRectItem

class MyCustomItem : public QGraphicsRectItem
{
  public:
    MyCustomItem(MyCustomItem* a_Parent = 0);
    virtual ~MyCustomItem();

    // specific methods

  private:
    // specific data
};

还假设您在 QML 脚本中定义了一个 ApplicationWindow:

// main.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.0

ApplicationWindow {
    id: myWindow
    title: qsTr("My Window")
    width: 640
    height: 480
    visible: true
}

我想做的简单任务是在 ApplicationWindow 中显示 MyCustomItem 的实例。我想执行以下操作:

// part of main.cpp
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    MyCustomItem* myItem;
    engine.rootContext()->setContextProperty("MyCustomItem", myItem);

    return app.exec();
}

但这当然行不通,因为MyCustomItem既不是QObject也不是QVariant。我不希望我的物品是 QGraphicsRectItem 之外的任何东西。是不是可以显示那个图形项目?那应该很简单,不是吗? QDeclarativeItem 有什么办法吗?我找不到解决这个问题的方法,这非常令人沮丧。如果我用 "normal" Qt 实现我的应用程序,问题就已经解决了,因为在这种情况下你有一个场景,场景有一个成员方法 addItem() 我不需要做复杂的东西将我的自定义图形项目添加到我的场景中。我是否必须将此项目包装在 QDeclarativeItemQObject 中才能完成?在我看来,那太可怕了。没有更好的选择吗?

编辑

难道 QGraphicsRectItem 不适合 class 继承,而像 QQuickPaintedItem 这样的东西(如评论中所建议的)会更合适吗?

我不能代表 Qt 4,但在 Qt 5 中,您有多种自定义绘图选项:

QQuickPaintedItem

基于 QPainter 的 QQuickItem。这听起来最接近你想要的。 one of the examples:

文档中的一个片段
void TextBalloon::paint(QPainter *painter)
{
    QBrush brush(QColor("#007430"));

    painter->setBrush(brush);
    painter->setPen(Qt::NoPen);
    painter->setRenderHint(QPainter::Antialiasing);

    painter->drawRoundedRect(0, 0, boundingRect().width(), boundingRect().height() - 10, 10, 10);

    if (rightAligned)
    {
        const QPointF points[3] = {
            QPointF(boundingRect().width() - 10.0, boundingRect().height() - 10.0),
            QPointF(boundingRect().width() - 20.0, boundingRect().height()),
            QPointF(boundingRect().width() - 30.0, boundingRect().height() - 10.0),
        };
        painter->drawConvexPolygon(points, 3);
    }
    else
    {
        const QPointF points[3] = {
            QPointF(10.0, boundingRect().height() - 10.0),
            QPointF(20.0, boundingRect().height()),
            QPointF(30.0, boundingRect().height() - 10.0),
        };
        painter->drawConvexPolygon(points, 3);
    }
}

Canvas

基于

JavaScript 的绘图 QML 类型 HTML5-like API. A snippet from one of the examples:

Canvas {
    id: canvas
    width: 320
    height: 250
    antialiasing: true

    property color strokeStyle: Qt.darker(fillStyle, 1.2)
    property color fillStyle: "#6400aa"

    property int lineWidth: 2
    property int nSize: nCtrl.value
    property real radius: rCtrl.value
    property bool fill: true
    property bool stroke: false
    property real px: width/2
    property real py: height/2 + 10
    property real alpha: 1.0

    onRadiusChanged: requestPaint();
    onLineWidthChanged: requestPaint();
    onNSizeChanged: requestPaint();
    onFillChanged: requestPaint();
    onStrokeChanged: requestPaint();

    onPaint: squcirle();

    function squcirle() {
        var ctx = canvas.getContext("2d");
        var N = canvas.nSize;
        var R = canvas.radius;

        N=Math.abs(N);
        var M=N;
        if (N>100) M=100;
        if (N<0.00000000001) M=0.00000000001;

        ctx.save();
        ctx.globalAlpha =canvas.alpha;
        ctx.fillStyle = "white";
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        ctx.strokeStyle = canvas.strokeStyle;
        ctx.fillStyle = canvas.fillStyle;
        ctx.lineWidth = canvas.lineWidth;

        ctx.beginPath();
        var i = 0, x, y;
        for (i=0; i<(2*R+1); i++){
            x = Math.round(i-R) + canvas.px;
            y = Math.round(Math.pow(Math.abs(Math.pow(R,M)-Math.pow(Math.abs(i-R),M)),1/M)) + canvas.py;

            if (i == 0)
                ctx.moveTo(x, y);
            else
                ctx.lineTo(x, y);
        }

        for (i=(2*R); i<(4*R+1); i++){
            x =Math.round(3*R-i)+canvas.px;
            y = Math.round(-Math.pow(Math.abs(Math.pow(R,M)-Math.pow(Math.abs(3*R-i),M)),1/M)) + canvas.py;
            ctx.lineTo(x, y);
        }
        ctx.closePath();
        if (canvas.stroke) {
            ctx.stroke();
        }

        if (canvas.fill) {
            ctx.fill();
        }
        ctx.restore();
    }
}

QSGGeometryNode

this answer, you could take advantage of the Qt Quick Scene Graph. A snippet from one of the examples所述:

QSGNode *BezierCurve::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
    QSGGeometryNode *node = 0;
    QSGGeometry *geometry = 0;

    if (!oldNode) {
        node = new QSGGeometryNode;
        geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), m_segmentCount);
        geometry->setLineWidth(2);
        geometry->setDrawingMode(GL_LINE_STRIP);
        node->setGeometry(geometry);
        node->setFlag(QSGNode::OwnsGeometry);
        QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
        material->setColor(QColor(255, 0, 0));
        node->setMaterial(material);
        node->setFlag(QSGNode::OwnsMaterial);
    } else {
        node = static_cast<QSGGeometryNode *>(oldNode);
        geometry = node->geometry();
        geometry->allocate(m_segmentCount);
    }

    QRectF bounds = boundingRect();
    QSGGeometry::Point2D *vertices = geometry->vertexDataAsPoint2D();
    for (int i = 0; i < m_segmentCount; ++i) {
        qreal t = i / qreal(m_segmentCount - 1);
        qreal invt = 1 - t;

        QPointF pos = invt * invt * invt * m_p1
                    + 3 * invt * invt * t * m_p2
                    + 3 * invt * t * t * m_p3
                    + t * t * t * m_p4;

        float x = bounds.x() + pos.x() * bounds.width();
        float y = bounds.y() + pos.y() * bounds.height();

        vertices[i].set(x, y);
    }
    node->markDirty(QSGNode::DirtyGeometry);

    return node;
}

QQuickWidget

如果你真的想使用 QGraphicsItem 子类,你可以反其道而行之,并拥有一个包含某些 "Qt Quick Widgets" 的基于小部件的应用程序,尽管这不是最佳选择(参见 Qt Weekly #16: QQuickWidget了解更多信息)。