在QT場景視圖中,一個2D圖形項是一個QGraphicsItem,我們可以通過繼承來定義我們自己的圖形項。
主要有以下三個虛函數需要重點關注:
1) 邊界矩形(必須實現)
virtual QRectF boundingRect() const = 0;
2) 圖形形狀(可選實現),該函數返回圖形項的實際形狀路徑,常用於碰撞檢測、命中測試等等,默認實現返回boundingRect的矩形形狀(具體的圖形項的形狀是任意變化的,默認的矩形形狀顯然不能正確表示圖形的實際形狀,所以建議重寫該函數)。需要注意的是,形狀的輪廓線可能會根據畫筆大小以及線型而有所不同,所以實際的形狀也應該包括輪廓線的區域。
virtual QPainterPath shape() const;
3) 圖形內容(必須實現)
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) = 0;
圖形一般的表現形式有兩種:封閉和非封閉,如直線、曲線等都是非封閉圖形,而矩形、橢圓等為封閉圖形,非封閉圖形無法使用填充,實際的形狀為線條所指定的路徑區域,封閉圖形可以使用填充,實際的形狀包括線條以及封閉填充區域。
QT的QPainter類提供了繪制最常見圖形(如矩形、橢圓、多邊形、文本等)API,對於一些不規則形狀的復雜圖形,則提供了drawPath方法通過繪制路徑來達到。
QPainterPath
QPainterPath 類(繪圖路徑)提供了一個容器,用於繪圖操作,可以創建和重用圖形形狀。
繪圖路徑是由許多圖形化的構建塊組成的對象,例如:矩形、橢圓、直線和曲線。構建塊可以加入在封閉的子路徑中,例如:矩形或橢圓。封閉的路徑的起點和終點是一致的,或者他們可以作為未封閉的子路徑獨立存在,如:直線和曲線。
與正常繪圖相比,QPainterPath 的主要優點在於:復雜的圖形只需創建一次,然后只需調用 QPainter::drawPath() 函數即可繪制多次。QPainterPath 提供了一組函數,可用於獲取繪圖路徑及其元素的信息。除了可以使用 toReversed() 函數來改變元素的順序外,還有幾個函數將 QPainterPath 對象轉換成一個多邊形表示。
QPainterPathStroker
QPainterPath 可以被填充(fill)、描繪輪廓(outline)、裁剪(clip)。要為一個指定的繪圖路徑生成可填充的輪廓,可以使用 QPainterPathStroker 類。。
通過調用createStroke()函數,將給定的QPainterPath作為參數傳遞,將創建一個表示給定路徑輪廓的新畫家路徑(outlinepath)。 然后可以填充新創建的畫家路徑用於繪制原始畫家路徑(path)的輪廓。
您可以使用以下函數控制輪廓的各種設計方面(畫筆寬度,帽子樣式,連接樣式和點畫線模式):
- setWidth()
- setCapStyle()
- setJoinStyle()
- setDashPattern()
setDashPattern()函數既可以接受Qt::PenStyle對象,也可以接受模式的vector表示作為參數。
此外,您可以使用setCurveThreshold()函數指定曲線的閾值,控制繪制曲線的粒度。默認閾值是經過良好調整的值(0.25),通常您不需要修改它。但是,您可以通過降低其值來使曲線的外觀更平滑。
您還可以使用setMiterLimit()函數控制生成的輪廓的斜接限制。斜接限制描述了斜接連接可以延伸到每個連接的距離。限制以寬度為單位指定,因此像素化斜接限制將為miterlimit * width。僅當連接樣式為Qt :: MiterJoin時才使用此值。
注意,createStroke()函數生成的painter路徑只能用於概述給定的painter路徑,否則可能會導致意外行為。生成的輪廓也需要默認設置的Qt :: WindingFill規則。
QT場景視圖中要實現2D圖形的精准拾取,就需要關注圖形的shape而不是boundingRect,下面是一個測試例子,僅供參考:
新建ItemBase類,繼承自QGraphicsItem,用於規定子Item的一些共同行為:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#ifndef
ITEMBASE_H #define ITEMBASE_H #include <QGraphicsItem> class QGraphicsSceneMouseEvent; class ItemBase : public QGraphicsItem { public : ItemBase(QSize size, QGraphicsItem *parent = nullptr); virtual ~ItemBase() override; QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; protected : void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; void wheelEvent(QGraphicsSceneWheelEvent *event) override; bool isInResizeArea( const QPointF &pos); protected : QSize m_size; private : bool m_isResizing; bool m_isRotating; }; #endif // ITEMBASE_H |
在ItemBase類中,我們重寫了boundingRect以及paint函數,圖形項的具體形狀在子類中重寫shape()來實現:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
QRectF ItemBase::boundingRect()
const
{ // 實際圖形形狀的邊界矩形 return shape().boundingRect(); } void ItemBase::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(widget); if (option->state & QStyle::State_Selected) { painter->setRenderHint(QPainter::Antialiasing, true ); if (option->state & QStyle::State_HasFocus) { painter->setPen(QPen(Qt::yellow, 3 )); } else { painter->setPen(Qt::white); } painter->drawRect(boundingRect()); } painter->setRenderHint(QPainter::Antialiasing, false ); } |
以ItemPolyline為例進行說明:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
#ifndef
ITEMPOLYLINE_H #define ITEMPOLYLINE_H #include "ItemBase.h" class ItemPolyline : public ItemBase { public : ItemPolyline(QSize size, QGraphicsItem *parent = nullptr); virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr); // overwrite shape() QPainterPath shape() const ; }; #endif // ITEMPOLYLINE_H #include "ItemPolyline.h" #include <QPainter> #include <QPainterPath> ItemPolyline::ItemPolyline(QSize size, QGraphicsItem *parent) : ItemBase (size, parent) { } void ItemPolyline::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { static const QPointF points[ 3 ] = { QPointF( 10 . 0 , 100 . 0 ), QPointF( 20 . 0 , 10 . 0 ), QPointF( 100 . 0 , 30 . 0 ), }; painter->save(); QPen pen(Qt::blue); pen.setWidth( 10 ); pen.setJoinStyle(Qt::MiterJoin); // MiterJoin, BevelJoin, RoundJoin pen.setCapStyle(Qt::RoundCap); // FlatCap, SquareCap, RoundCap pen.setStyle(Qt::DashLine); painter->setPen(pen); painter->drawPolyline(points, 3 ); painter->restore(); ItemBase::paint(painter, option, widget); } QPainterPath ItemPolyline::shape() const { static const QPointF points[ 3 ] = { QPointF( 10 . 0 , 100 . 0 ), QPointF( 20 . 0 , 10 . 0 ), QPointF( 100 . 0 , 30 . 0 ), }; QPainterPath path; path.moveTo(points[ 0 ]); path.lineTo(points[ 1 ]); path.lineTo(points[ 2 ]); QPainterPathStroker stroker; stroker.setWidth( 10 ); stroker.setJoinStyle(Qt::MiterJoin); stroker.setCapStyle(Qt::RoundCap); stroker.setDashPattern(Qt::DashLine); return stroker.createStroke(path); } |
ItemPolyline類中重寫shape()函數,使用QPainterPath和QPainterPathStroker比較精准地獲取了圖形的輪廓形狀,有利於鼠標對圖形的精准拾取。注意:對於封閉形狀,既要考慮其形狀所圍填充區域,又要考慮其邊界輪廓的寬度區域。
除了Polyline外,我還做了Rectangle、Ellipse、Bezier、ClosedBezier以及line和lines等2D圖形,以下是運行截圖:
鼠標點擊2D圖形的有效區域(即Shape所規定的路徑區域)會比較精准地選中圖形,而其它空白區域則無法選中,僅供參考,歡迎交流!