QGraphicsItem鼠標精准拾取(pick/select)研究


在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的一些共同行為:

ItemBase.h 
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()來實現:

ItemBase.cpp
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為例進行說明:

 C++ Code 
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所規定的路徑區域)會比較精准地選中圖形,而其它空白區域則無法選中,僅供參考,歡迎交流!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM