在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, ));
        }
        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)
{
    ] =
    {
        QPointF(),
        QPointF(),
        QPointF(),
    };

painter->save();
    QPen pen(Qt::blue);
    pen.setWidth();
    pen.setJoinStyle(Qt::MiterJoin);    // MiterJoin, BevelJoin, RoundJoin
    pen.setCapStyle(Qt::RoundCap);      // FlatCap, SquareCap, RoundCap
    pen.setStyle(Qt::DashLine);
    painter->setPen(pen);
    painter->drawPolyline(points, );
    painter->restore();

ItemBase::paint(painter, option, widget);
}

QPainterPath ItemPolyline::shape() const
{
    ] =
    {
        QPointF(),
        QPointF(),
        QPointF(),
    };
    QPainterPath path;
    path.moveTo(points[]);
    path.lineTo(points[]);
    path.lineTo(points[]);
    QPainterPathStroker stroker;
    stroker.setWidth();
    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所规定的路径区域)会比较精准地选中图形,而其它空白区域则无法选中,仅供参考,欢迎交流!

QGraphicsItem鼠标精准拾取(pick/select)研究的更多相关文章

  1. QQuickPaintedItem鼠标精准拾取(pick/select)研究

    QT C++在2D图形方面已经做的很完善了,在PC端(Windows.Linux和MaC)上都有很好的表现. QT中的QML特别适合于移动端应用的开发,QML中的一些基本形状类型并不是一一地与Qt C ...

  2. QGraphicsItem鼠标旋转控制研究

    在QT场景视图中2D图形项Item的基类为QGraphicsItem,如果我们需要自定义Item则可以从其派生,然后重写boundingRect以及paint虚函数实现图形项的外边界定义以及内容绘制工 ...

  3. Micro LED巨量转移技术研究进展

    近年来,Micro LED因其功耗低.响应快.寿命长.光效率高等特点,被视为继LCD.OLED之后的新一代显示面板技术.Micro LED的英文全名是Micro Light Emitting Diod ...

  4. OpenGL中的拾取模式( Picking)

    1. Opengl中的渲染模式有三种:(1)渲染模式,默认的模式:(2)选择模式, (3)反馈模式.如下 GLint glRenderMode(GLenum mode) mode可以选取以下三种模式之 ...

  5. Selenium:利用select模块处理下拉框

    在利用selenium进行UI自动化测试过程中,经常会遇到下拉框选项,这篇博客,就介绍下如何利用selenium的Select模块来对标准select下拉框进行操作... 首先导入Select模块: ...

  6. js实现鼠标拖动框选元素小狗

    方法一: <html> <head></head> <style> body{padding:100px;} .fileDiv{float:left;w ...

  7. WebGL射线拾取模型——八叉树优化

    经过前面2篇WebGL射线拾取模型的文章,相信大家对射线和模型面片相交的原理已经有所了解,那么今天我们再深入探究关于射线拾取的一个问题,那就是遍历场景中的所有与射线相交的模型的优化问题.首先我们来复习 ...

  8. 微软的鼠标 Microsoft mouse

    微软是做软件出身的厂商, 所以微软开发的软件质量毋庸置疑,Windows操作系统还有诸如Office的办公软件拥有庞大的用户群. 微软家的Visual Studio也被号称宇宙最强IDE,我个人也每天 ...

  9. Selenium: 利用select模块操作下拉框

    在利用selenium进行UI自动化测试过程中,经常会遇到下拉框选项,这篇博客,就介绍下如何利用selenium的Select模块来对标准select下拉框进行操作... 首先导入Select模块: ...

随机推荐

  1. 登录-退出,在T分钟实现BC次用户登录退出,单次登录-退出%90用户时间t,需要的并发用户(线程)

    聚合报告%90响应时间:%90用户响应时小于该值 2种理解方式: 一. 1s可完成的用户1/t: T分钟完成的用户T *(1/t); BC次用户需要的线程数Thread= BC/(T*(1/t)) = ...

  2. luoguP2882Face The Right Way

    https://www.luogu.org/problem/P2882 题意 你有n头牛,每头牛有个朝向,你每次可以选择连续k头牛翻转,求k为多少时可以用最少的步骤将所有牛朝向变为正向 n≤5000 ...

  3. LVS(二):四种工作模型

    面试的时候必问这个四种工作模式,因为这几乎是企业里面必用的内容,所以一定要将其理解通透. 一.lvs-nat模式 二.LVS-DR模式(默认) 三.LVS-tun模式 四.LVS-fullnat模式 ...

  4. 429 too many requests错误出现在wordpress后台更新及官网的5种解决方法

    从今年10月份开始wordpress服务经常出现429 too many requests错误,包括后台更新和访问wp官网,如下图所示,这是为什么呢?怎么处理呢?有大佬向官方论坛提问了,论坛主持人Ja ...

  5. woocommerce调用分类描述另一种方法

    woocommerce是在wordpress基础上开发的,有些函数是通用的,有些需要自己调用,比如文章分类用category_description()就可以调用,而woocommerce的产品分类描 ...

  6. Zookeeper注册中心搭建-单机版(三)

    Zookeeper是一个分布式协调组件,本质是一个软件. Zookeeper常用的功能有: 发布订阅功能,把 zookeeper 当作注册中心的原因. 分布式/集群管理功能 Zookeeper是Jav ...

  7. SPOJ - Triple Sums

    [传送门] FFT第一题! 构造多项式 $A(x) = \sum x ^ {s_i}$. 不考虑题目中 $i < j < k$ 的条件,那么 $A^3(x)$ 每一项对应的系数就是答案了. ...

  8. Shell编程——位置参数变量

    1. (1)创建Shell脚本: (2)赋予执行权限: (3)执行Shell脚本: (4)对脚本赋值后执行脚本: $n:n为数字,$0代表命令本身,$1-9代表第一到第九个参数,十以上的参数用大括号包 ...

  9. nowcoder907B n的约数

    题意 t次询问,每次给你一个数n,求在[1,n]内约数个数最多的数的约数个数 \(t \le 500,n \le 10^{19}\) 思路 首先可以想到将n质因数分解.即\(n= \prod\limi ...

  10. impala进阶

    一.impala存储 1.文件类型 2.压缩方式 二.impala分区 1.创建分区方式 partitioned by 创建表时,添加该字段指定分区列表: create table t_person( ...