自定义QGraphicsItem

来源:互联网 发布:阿里云固定电话 编辑:程序博客网 时间:2024/05/21 21:59

简述

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() 为视图设置场景,并显示视图。

4 0
原创粉丝点击