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所规定的路径区域)会比较精准地选中图形,而其它空白区域则无法选中,仅供参考,欢迎交流!

猜你喜欢

转载自www.cnblogs.com/MakeView660/p/11225406.html