2017-02-13 151 views
0

[1/2]背景

您好! Qt为我们提供了创建高度定制图形项目的意义。我们所需要做的就是继承QGraphicsItem并覆盖纯虚拟boundingRect()函数。此外,我们可以选择性地覆盖虚拟shape()函数以(除其他之外)为项目提供更精确的形状...Qt - QGraphics(异形)的选择项目

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

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(); 
} 

回答

2

你不需要做什么特别的期望从shape方法返回适当QPainterPath。如果您返回的路径是简单的,未封闭的路径,那么您必须完全按照该路径进行选择。您没有包含shape方法的代码,但这是问题所在。不应该有任何需要玩鼠标事件的游戏。

此外:

文档不说这个,但选择机制似乎从shape处理返回的路径为封闭路径它是否真的是。我能够通过使用stroker来修复这个问题,以返回轮廓:

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(); 
} 

这给出了你想要的选择行为。但是,这会引入一个新问题,因为您目前正在绘制shape的返回值。用这个新代码,曲线没有被填充。我推荐的是,您创建一个单独的方法来建立路径,然后使shapepaint获得该新方法的路径。例如:

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。这种方法更符合shape方法的用途。它用于碰撞检测和选择,而不是绘图。事实上,如果你的线条非常细,用户很难准确点击它们,所以通过stroker,你可以扩展轮廓路径的宽度,以便在曲线周围留出几个像素的缓冲区。这很好,但你不想得出结果。

+0

确实你是对的:我的形状功能是错误的。我改变它,以便它返回与画到屏幕相同的路径。 **但这并没有改变**。也许我失去了一些东西...... 然后,我编辑原始帖子添加一个片段,以便您可以编译,执行并查看我自己在说什么。 因为真实的代码更复杂,所以很难想出这个小小的代码片段。希望有人能为我提供一个解决方案。 – misterFad

+0

回答上面编辑提供修复。并感谢优秀的代码示例。我毫无疑问很难创造,但你提供的是完美的。 – goug

+0

哇https://translate.google.fr/#ja/en/subarashi!它像梦一样运作。非常感谢。 但是(是的,有一个但是),让我们说用于绘制路径的笔被赋予了一个非默认的样式,就像'pen.setStyle(Qt :: DashLine);'一样。然后选择曲线(以便画家的笔宽增加)。现在选择一个控制点并移动它。您会注意到绘制的路径有点奇怪:它不同于'shape'函数只是'return buildPath()'而不使用'QPainterPathStroker'的情况。 任何提示去解决这个问题? – misterFad