Qt - QGraphics(Shaped)项目的选择

时间:2017-02-13 23:56:40

标签: c++ qt

[1/2]上下文

你好! Qt为我们提供了创建高度自定义图形项目的意义。我们需要做的就是从QGraphicsItem继承并覆盖纯虚拟boundingRect()函数。此外,我们可以选择覆盖虚拟shape()函数(以及其他)为项目提供更准确的形状......

现在让我们看看下面的图表,我用软件(个人学生项目)绘制了我用C ++开发的Qt。

a quite beautiful graph

然后让灰色突出显示上图中每条边的边界矩形。

highlighting the edge bounding rectangle

[2/2]问题&备注

我希望这些项目可以选择,因此我启用了选择标记:

setFlag(ItemIsSelectable, true);

它像矩形和圆形物品的梦想。它也适用于边缘,但不像魅力。实际上,如果我单击由边界矩形(上图中的灰色矩形)定义的区域,则仍会选择边缘。 是否有办法确保只有点击定义项目形状的曲线时才会考虑鼠标点击事件?

我已经覆盖了所有鼠标*事件,如果shape()与event.scenePos()不相交,则返回,但结果并不好。有没有办法实现我想做的事情? 是否有Qt-ish方法检查鼠标位置是否在曲线路径内?

实际上我最终设置了一个标志,以便边缘忽略鼠标按钮:

setAcceptedMouseButtons(Qt::NoButton);

但如果有人遇到类似的问题并且有分享的解决方案,我会很高兴。

修改

以下是您可以编译和执行的部分代码。提醒一下,我只想在我们点击定义其形状的路径时选择边(曲线)。

/**
 * It was really hard to come up with the little snippet below,
 * since the real code is more complex.
 * Hope someone'll be able to provide me with a solution.
 * 
 * All you need to do is to copy and paste the code to a main.cpp file.
 */

#include <QApplication>

#include <QGraphicsItem>
#include <QGraphicsRectItem>
#include <QGraphicsView>
#include <QScrollBar>

/**
 * Nothing special about this class.
 * Note View instances handle rubber band selection (you can try it).
 */
class View : public QGraphicsView {
public:
    View(QWidget *parent = nullptr)
        : QGraphicsView(parent)
    {
        customize();
    }

private:
    void customize() // just customization
    {
        horizontalScrollBar()->setContextMenuPolicy(Qt::NoContextMenu);
        verticalScrollBar()->setContextMenuPolicy(Qt::NoContextMenu);

        setBackgroundBrush(QBrush(Qt::lightGray, Qt::CrossPattern));
        setRenderHint(QPainter::Antialiasing);
        setDragMode(RubberBandDrag);
        setRubberBandSelectionMode(Qt::ContainsItemShape);
    }
};

/**
 * Nothing special about this class, just a helper class.
 *
 * A rect item has the QGraphicsItem::ItemIsSelectable QGraphicsItem::ItemIsMovable enabled.
 * So you can select and move it around.
 */
class RectItem : public QGraphicsRectItem {
public:
    RectItem(QGraphicsItem *parent = nullptr)
        : QGraphicsRectItem(parent)
    {
        const double length = 10;
        setFlags(ItemIsSelectable | ItemIsMovable | ItemSendsGeometryChanges);
        setRect(-length/2.0, -length/2.0, length, length);
    }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = Q_NULLPTR) override
    {
        setBrush(isSelected() ? QBrush(Qt::gray) : Qt::NoBrush);
        QGraphicsRectItem::paint(painter, option, widget);
    }

protected:
    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
    {
        switch(change) {
        case ItemPositionChange: case ItemSelectedChange:
            if(scene()) {
                scene()->update(); // just to avoid some ugly effect occuring on the scene.
            }
            break;

        default:
            break;
        }

        return QGraphicsRectItem::itemChange(change, value);
    }
};

/**
 * A quite simple version of what a cubic Bezier curve is:
 *     it starts at a given point "from",
 *     ends at some point "to",
 *     having two control points (let's say "ctrlPt1" and ctrlPt2").
 *
 * A curve has the QGraphicsItem::ItemIsSelectable enabled.
 * So you can select it.
 */
class Curve : public QGraphicsItem {
protected:
    RectItem from;
    RectItem ctrlPt1;
    RectItem ctrlPt2;
    RectItem to;

public:
    Curve(QGraphicsItem *parent = nullptr)
        : QGraphicsItem(parent)
    {
        // simple customization

        setFlags(ItemIsSelectable);

        // set positions

        const qreal h = 100.;
        const qreal d = 100.;

        from.setPos(-150, 0);
        ctrlPt1.setPos(from.pos() + QPointF(d, -h));
        ctrlPt2.setPos(ctrlPt1.pos() + QPointF(d, 0));
        to.setPos(ctrlPt2.x()+d, ctrlPt2.y()+h);
    }

    // Should be called after scene is defined for this item.
    void addPoints() {
        QList<QGraphicsRectItem*> list;
        list << &from << &ctrlPt1 << &ctrlPt2 << &to;
        for(auto *item : list) {
            scene()->addItem(item);
        }
    }

    QRectF boundingRect() const override
    {
        QPolygonF poly;
        poly << from.pos() << ctrlPt1.pos() << ctrlPt2.pos() << to.pos();

        return poly.boundingRect()
              .normalized();
    }

    QPainterPath shape() const override
    {
        QPainterPath path;
        path.moveTo(from.pos());
        path.cubicTo(ctrlPt1.pos(), ctrlPt2.pos(), to.pos());

        return path;
    }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = Q_NULLPTR) override
    {
        Q_UNUSED(option)
        Q_UNUSED(widget)

        // Draw curve

        QPen pen = QPen(Qt::darkBlue);
        pen.setWidthF(isSelected() ? 3. : 1.);
        painter->setPen(pen); // curve pen
        painter->setBrush(Qt::green); // curve brush

        painter->drawPath(shape());

        // Tie ctrl points

        const bool tieCtrlPoints = from.isSelected() || ctrlPt1.isSelected() || ctrlPt2.isSelected() || to.isSelected();
        if(tieCtrlPoints) {
            painter->setPen(Qt::black);
            painter->setBrush(Qt::black);

            painter->drawLine(from.pos(), ctrlPt1.pos());
            painter->drawLine(ctrlPt1.pos(), ctrlPt2.pos());
            painter->drawLine(ctrlPt2.pos(), to.pos());
        }
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QGraphicsScene scene;
    scene.setSceneRect(-300, -300, 600, 600);

    View view;
    view.setScene(&scene);
    Curve curve;
    scene.addItem(&curve);
    curve.addPoints();

    view.show();

    return a.exec();
}

1 个答案:

答案 0 :(得分:2)

您不需要做任何特别的事情,期望从for sheet in workbook.sheetnames: print(sheet) 方法返回适当的QPainterPath。如果返回的路径是一个简单的未闭合路径,则必须完全单击该路径才能选择。您没有包含shape方法的代码,但问题出在哪里。不应该用鼠标事件玩游戏。

此外:

文档没有说明这一点,但选择机制似乎将shape的返回路径视为封闭路径,无论它是否实际存在。我能够通过使用一个stroker返回轮廓来解决这个问题:

shape

这给出了您想要的选择行为。但是,这会引入一个新问题,因为您当前正在绘制QPainterPath shape() const override { QPainterPath path; path.moveTo(from.pos()); path.cubicTo(ctrlPt1.pos(), ctrlPt2.pos(), to.pos()); QPainterPathStroker stroker; return stroker.createStroke (path).simplified (); } 的返回值。使用这个新代码,曲线不会被填充。我建议您创建一个单独的方法来构建路径,然后让shapeshape从该新方法获取路径。例如:

paint

然后在绘画中,让它调用QPainterPath buildPath () const { QPainterPath path; path.moveTo(from.pos()); path.cubicTo(ctrlPt1.pos(), ctrlPt2.pos(), to.pos()); return path; } QPainterPath shape() const override { QPainterPath path = buildPath(); QPainterPathStroker stroker; return stroker.createStroke (path).simplified (); } 而不是buildPath。这种方法更符合shape方法的目的。它用于碰撞检测和选择,不用于绘图。事实上,如果你的线很细,你的用户可能很难准确地点击它们,所以使用stroker,你可以扩展轮廓路径的宽度,以允许曲线周围有几个像素的缓冲区。这很有效,但你不想画出那个结果。