zoukankan      html  css  js  c++  java
  • 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.0100.0),
            QPointF(
    20.010.0),
            QPointF(
    100.030.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.0100.0),
            QPointF(
    20.010.0),
            QPointF(
    100.030.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所规定的路径区域)会比较精准地选中图形,而其它空白区域则无法选中,仅供参考,欢迎交流!

  • 相关阅读:
    递归
    书评:《C程序设计语言》
    下一代互联网,今日揭开面纱:IPv6真的要来了
    庆祝Alan Mathison Turing(艾伦·图灵)诞辰100周年!
    svn常用命令行和批处理
    ORACLE 9i数据导入到ORACLE 10G中文出现的乱码问题
    Oracle 11G 的客户端,不再支持连接到ORACLE 8I
    DB2 一个汉字的Byte数,太操蛋了
    关于Windows 7 64位下Visual Studio 2010 开发的Asp.net程序连接Oracle 的出现的问题
    Web Frame 跨域调用Session 丢失问题
  • 原文地址:https://www.cnblogs.com/MakeView660/p/11225406.html
Copyright © 2011-2022 走看看