在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. python gyp

    https://github.com/bnoordhuis/gyp 所以,手动加了这个变量 https://blog.csdn.net/weixin_30576827/article/details/ ...

  2. 20180711模拟赛T3——聚变

    文件名: fusion 题目类型: 传统题 时间限制: 3秒 内存限制: 256MB 编译优化: 无 题目描述 知名科学家小A在2118年在计算机上实现了模拟聚变的过程. 我们将她研究的过程简化. 核 ...

  3. Linux系统下root密码遗忘等系统故障的修复方法 - 运维总结

    IDC机房有一台centos系统的服务器,由于这台服务器的系统装了好长时间,且root密码中间更新过几次,后面去机房现场维护时,登陆密码遗忘了,悲催啊~没办法,只能开机进入“单用户模式”进行密码重置了 ...

  4. sqler 2.2 发布了,支持定时任务以及触发器

    sqler 在10前发布了,2.2 添加了定时任务以及触发器(webhook),都是比较方便的功能, 同时我也修改了dockerfile, 做了构建,添加了功能支持,同时push 到了dockerhu ...

  5. Linux性能优化实战学习笔记:第五十四讲

    一.上节回顾 上一节,我带你学习了,如何使用 USE 法来监控系统的性能,先简单回顾一下. 系统监控的核心是资源的使用情况,这既包括 CPU.内存.磁盘.文件系统.网络等硬件资源,也包括文件描述符数. ...

  6. MySQL实战45讲学习笔记:第十四讲

    一.引子 在开发系统的时候,你可能经常需要计算一个表的行数,比如一个交易系统的所有变更记录总数.这时候你可能会想,一条 select count(*) from t 语句不就解决了吗? 但是,你会发现 ...

  7. [LeetCode] 327. Count of Range Sum 区间和计数

    Given an integer array nums, return the number of range sums that lie in [lower, upper] inclusive.Ra ...

  8. [LeetCode] 153. Find Minimum in Rotated Sorted Array 寻找旋转有序数组的最小值

    Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e. ...

  9. 虚拟机安装CentOS 7

  10. vue需求表单有单位(时分秒千克等等)

    需求如下: 问题分析: 因为用elementui组件 el-input 相当于块级元素,后面的单位<span>分</span>会被挤下去,无法在同一水平. 解决方法: 不用它的 ...