当前位置: 首页 > news >正文

自定义QGraphicsItem

简述

QGraphicsItem 是场景中 item 的基类。图形视图提供了一些典型形状的标准 item,例如:矩形 ( QGraphicsRectItem )、椭圆 ( QGraphicsEllipseItem ) 、文本项 ( QGraphicsTextItem )。当这些不满足需求时(例如:在一些复杂的工作流场景中),往往需要自定义,通常的做法就是继承 QGraphicsItem。

  • 简述
  • 自定义 QGraphicsItem
  • Bounding Rect 和 Shape
  • 参照模型
  • 使用示例
    • 效果
    • 源码

自定义 QGraphicsItem

要实现自定义 item,需要覆盖 QGraphicsItem 的两个纯虚函数:

  • void paint()
    • 以本地坐标绘制 item 的内容
  • QRectF boundingRect()
    • 将 item 的外边界作为矩形返回
    • 由 QGraphicsView 调用以确定什么区域需要重绘

除此之外,可能还需要附加其他需求,例如:

  • QPainterPath shape() - item 的形状
    • 由 contains() 和 collidesWithPath() 用于碰撞检测
    • 如果未实现,则默认为 boundingRect()
  • 使用信号/槽、属性机制:继承 QObject 和 QGraphicsItem(或直接继承 QGraphicsObject)
  • 处理鼠标事件:重新实现 mouse***Event()
  • 处理键盘事件:重新实现 key***Event()
  • 处理拖放事件:重新实现 drag***Event()、dropEvent()
    ……

关于信号/槽、事件、算法相关的内容,本节暂时不做讲解,放到后面章节。

Bounding Rect 和 Shape

先来一张效果图,解释 Bounding Rect 和 Shape 的联系与区别:

这里写图片描述

  • Bounding Rect

    将 item 的外边界定义为矩形,所有绘制必须限制在此区域内,QGraphicsView 使用它来确定 item 是否需要重绘。

    虽然 item 的形状可以是任意的(例如:直线、椭圆、矩形 ),但是 bounding rect 总是矩形,并且不受 item 变换的影响。

  • Shape

    以本地坐标中的 QPainterPath 形式返回 item 的形状。形状可用于许多事情,包括:碰撞检测,命中测试以及 QGraphicsScene::items() 函数。

    shape() 默认实现调用 boundingRect() 返回一个简单的矩形形状,但子类可以重新实现该函数,以返回非矩形 item 更准确的形状。例如,一个圆形 item 可以选择返回椭圆形状,以便更好地进行碰撞检测。

    shape() 由 contains() 和 collidesWithPath() 的默认实现调用。

参照模型

来一个笑脸,瞬间萌萌哒……笑一笑,十年少!

这里写图片描述

要实现这个效果很简单,可以逐步分解:

  • 整体(最外侧的圆)
  • 眼睛(左眼/右眼/眼球)
  • 嘴(笑容)

分别计算出各部分的区域坐标、大小,然后根据形状进行绘制。

上述图案标识的是绝对位置,为了适应各种大小, 可以进行比例及相对位置换算,将各部分进行逐一转换。

使用示例

效果

下图显示了 3 个不同大小的笑脸:

这里写图片描述

源码

根据以上思路,我们可以很快的实现一个自定义的笑脸 - SmileItem。

SmileItem.h:

#ifndef SMILE_ITEM_H
#define SMILE_ITEM_H

#include <QGraphicsItem>

class SmileItem : public QGraphicsItem
{
public:
    explicit SmileItem(QGraphicsItem *parent = Q_NULLPTR);
    explicit SmileItem(const QRectF &rect, QGraphicsItem *parent = Q_NULLPTR);
    explicit SmileItem(qreal x, qreal y, qreal w, qreal h, QGraphicsItem *parent = Q_NULLPTR);
    ~SmileItem();
    QRectF rect() const;
    void setRect(const QRectF &rect);
    inline void setRect(qreal x, qreal y, qreal w, qreal h);

    QRectF boundingRect() const Q_DECL_OVERRIDE;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) Q_DECL_OVERRIDE;

private:
    void updateRect();

private:
    QRectF m_rect;
    mutable QRectF m_boundingRect;

    // 缩放比例
    double m_dScale;

    // 左眼、右眼、嘴的中点
    QPointF m_leftEyeCenter;
    QPointF m_rightEyecenter;
    QPointF m_smileCenter;

    // 眼睛的宽度、高度
    double m_dEyeWidth;
    double m_dEyeHeight;

    // 眼球宽度(高和宽相同)
    double m_dEyeBallWidth;

    // 嘴的高度、宽度
    double m_dSmileWidth;
    double m_dSmileHeight;
};

inline void SmileItem::setRect(qreal ax, qreal ay, qreal w, qreal h)
{
    setRect(QRectF(ax, ay, w, h));
}

#endif  //SMILE_ITEM_H

除了上述必须实现的两个函数之外,我们还提供一些额外的接口,例如:setRect() 来更改 item 的大小,在更新大小之后,则会调用 updateRect() 来重新计算笑脸中各个部位的坐标、大小。

SmileItem.cpp:

#include <QPainter>
#include "SmileItem.h"

SmileItem::SmileItem(QGraphicsItem *parent)
    : QGraphicsItem(parent)
{
    setRect(QRect(-50, -50, 100, 100));
}

SmileItem::SmileItem(const QRectF &rect, QGraphicsItem *parent)
    : QGraphicsItem(parent)
{
    setRect(rect);
}

SmileItem::SmileItem(qreal x, qreal y, qreal w, qreal h, QGraphicsItem *parent)
    : QGraphicsItem(parent)
{
    setRect(x, y, w, h);
}

SmileItem::~SmileItem()
{
}

QRectF SmileItem::rect() const
{
    return m_rect;
}

void SmileItem::setRect(const QRectF &rect)
{
    if (m_rect == rect)
        return;

    prepareGeometryChange();
    m_rect = rect;
    m_boundingRect = QRectF();
    updateRect();
    update();
}

QRectF SmileItem::boundingRect() const
{
    if (m_boundingRect.isNull())
        m_boundingRect = m_rect;

    return m_boundingRect;
}

void SmileItem::updateRect()
{
    // 缩放比例
    m_dScale = m_rect.width() / 100.0;

    // 左眼的中点
    m_leftEyeCenter.setX(-15 * m_dScale);
    m_leftEyeCenter.setY(- 25 * m_dScale);

    // 右眼的中点
    m_rightEyecenter.setX(15 * m_dScale);
    m_rightEyecenter.setY(- 25 * m_dScale);

    // 嘴的中点
    m_smileCenter.setX(0);
    m_smileCenter.setY(10 * m_dScale);

    // 眼睛的宽度、高度(宽度的 2 倍)
    m_dEyeWidth = m_rect.width() / (100.0 / 12);
    m_dEyeHeight = m_dEyeWidth * 2;

    // 眼球为眼睛大小的 1/4
    m_dEyeBallWidth = m_dEyeWidth / 4;

    // 嘴的高度、宽度
    m_dSmileWidth = m_rect.width() / (100.0 / 66);
    m_dSmileHeight = m_rect.height() / (100.0 / 50);
}

void SmileItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    // 反走样
    painter->setRenderHint(QPainter::Antialiasing, true);

    // 脸
    painter->setPen(Qt::NoPen);
    painter->setBrush(Qt::yellow);
    painter->drawEllipse(m_rect);

    // 左眼
    painter->setPen(QPen(Qt::black));
    painter->setBrush(Qt::white);
    painter->drawEllipse(QRectF(m_leftEyeCenter.x() - m_dEyeWidth / 2, m_leftEyeCenter.y() - m_dEyeHeight / 2, m_dEyeWidth, m_dEyeHeight));

    // 左眼球
    painter->setPen(QPen(Qt::black));
    painter->setBrush(Qt::black);
    painter->drawEllipse(QRectF(m_leftEyeCenter.x() - m_dEyeBallWidth / 2, m_leftEyeCenter.y() - m_dEyeBallWidth / 2, m_dEyeBallWidth / 2, m_dEyeBallWidth / 2));

    // 右眼
    painter->setPen(QPen(Qt::black));
    painter->setBrush(Qt::white);
    painter->drawEllipse(QRectF(m_rightEyecenter.x() - m_dEyeWidth / 2, m_rightEyecenter.y() - m_dEyeHeight / 2, m_dEyeWidth, m_dEyeHeight));

    // 右眼球
    painter->setPen(QPen(Qt::black));
    painter->setBrush(Qt::black);
    painter->drawEllipse(QRectF(m_rightEyecenter.x() - m_dEyeBallWidth / 2, m_rightEyecenter.y() - m_dEyeBallWidth / 2, m_dEyeBallWidth / 2, m_dEyeBallWidth / 2));

    // 嘴 - 笑容
    painter->setPen(QPen(Qt::red));
    painter->setBrush(Qt::NoBrush);

    QPainterPath path;
    path.arcMoveTo(QRectF(- m_dSmileWidth / 2, - (m_dSmileHeight / 2 - m_smileCenter.y()), m_dSmileWidth, m_dSmileHeight), 0);
    path.arcTo(QRectF(- m_dSmileWidth / 2, - (m_dSmileHeight / 2 - m_smileCenter.y()), m_dSmileWidth, m_dSmileHeight), 0, -180);
    painter->drawPath(path);
}

正如 setRect(),无论以任何方式更改 item 的几何形状,必须首先调用prepareGeometryChange(),以保证 QGraphicsScene 中的索引是最新的。

为了实现大小的自适应,在 updateRect() 中实现了坐标、大小的换算。并通过调用 update() 重新对 item 进行绘制。

然后就可以使用了:

// 定义笑脸
SmileItem *pItem = new SmileItem();
pItem->setRect(QRect(-25, -25, 50, 50));
pItem->setPos(10, 50);

SmileItem *pItem2 = new SmileItem();
pItem2->setRect(QRect(-50, -50, 100, 100));
pItem2->setPos(100, 50);

SmileItem *pItem3 = new SmileItem();
pItem3->setRect(QRect(-75, -75, 150, 150));
pItem3->setPos(250, 50);

// 将笑脸添加至场景中
QGraphicsScene *pScene = new QGraphicsScene();
pScene->addItem(pItem);
pScene->addItem(pItem2);
pScene->addItem(pItem3);

// 为视图设置场景
QGraphicsView *pView = new QGraphicsView();
pView->setScene(pScene);
pView->setStyleSheet("border:none; background:transparent;");

pView->show();

首先,构造三个不同大小的笑脸,并调用 setPos() 设置它们的位置。然后通过 QGraphicsScene::addItem() 将笑脸添加至场景中。最后,调用 QGraphicsView::setScene() 为视图设置场景,并显示视图。

相关文章:

  • FZU-2087 统计树边(最小生成树)
  • 选购线PHPUnit最佳实践:从入门到精通
  • 日常小算法
  • Cacti 监控流入流出差并作阈值报警
  • 精通Web Analytics 2.0 (7) 第五章:荣耀之钥:度量成功
  • scapy框架安装和入门
  • python知识点记录(一):
  • jdk之jhat命令
  • 解读基于数加的大数据仓库解决方案
  • EBS_DBA_技能:常用SQL
  • angularjs中,页面部分元素是通过ng-show来控制其否显示的,页面刚加载时,会闪烁,出来一下又隐藏...
  • 第一百一十二节,JavaScript浏览器检测
  • java分享第二十天(build.xml的语法及写法)
  • 如何下载百度网盘已失效资源链接
  • opencv 2.4.9+pcl 1.6+vs2010+win7 32开发环境配置
  • conda常用的命令
  • Golang-长连接-状态推送
  • maya建模与骨骼动画快速实现人工鱼
  • React Transition Group -- Transition 组件
  • Web设计流程优化:网页效果图设计新思路
  • 阿里云Kubernetes容器服务上体验Knative
  • 表单中readonly的input等标签,禁止光标进入(focus)的几种方式
  • 工作中总结前端开发流程--vue项目
  • 湖南卫视:中国白领因网络偷菜成当代最寂寞的人?
  • 开发了一款写作软件(OSX,Windows),附带Electron开发指南
  • 两列自适应布局方案整理
  • 区块链共识机制优缺点对比都是什么
  • 时间复杂度与空间复杂度分析
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 协程
  • 一个JAVA程序员成长之路分享
  • 用简单代码看卷积组块发展
  • 终端用户监控:真实用户监控还是模拟监控?
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • 数据库巡检项
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (android 地图实战开发)3 在地图上显示当前位置和自定义银行位置
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (附源码)ssm跨平台教学系统 毕业设计 280843
  • (九)信息融合方式简介
  • (算法)Game
  • (原創) 如何安裝Linux版本的Quartus II? (SOC) (Quartus II) (Linux) (RedHat) (VirtualBox)
  • (正则)提取页面里的img标签
  • (转) RFS+AutoItLibrary测试web对话框
  • (转)大型网站架构演变和知识体系
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • .CSS-hover 的解释
  • .NET 5.0正式发布,有什么功能特性(翻译)
  • .Net Core 中间件验签
  • .NET/C# 使窗口永不激活(No Activate 永不获得焦点)
  • .net图片验证码生成、点击刷新及验证输入是否正确
  • .Net中ListT 泛型转成DataTable、DataSet
  • [ web基础篇 ] Burp Suite 爆破 Basic 认证密码
  • [ 数据结构 - C++]红黑树RBTree