2017-02-13 52 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