qt-C++笔记之自定义绘制:QWidget中的paintEvent 与 QGraphicsItem中的paint

在这里插入图片描述
code review!

1. 概述

Qt 提供两种主要自定义绘制机制:QWidgetpaintEvent 用于传统 UI 部件的像素级绘制,适合简单控件自定义;QGraphicsItempaint 用于 Graphics View 框架中的图形项,适合复杂场景、动画和交互。两者均依赖 QPainter 进行绘制,但上下文、参数和优化不同。以下从 QWidget 开始,逐步扩展到 Graphics View。

2. QWidget 的 paintEvent

2.1. 基本概念

paintEventQWidget 及其子类(如 QMainWindowQLabel)中的虚函数,用于处理控件绘制事件。每当控件需要重绘时(如窗口最小化恢复、调用 update()repaint()),Qt 自动调用该函数。

2.2. 典型用法

继承 QWidget,重写 paintEvent

#include <QWidget>
#include <QPainter>

class MyWidget : public QWidget {
    Q_OBJECT
public:
    explicit MyWidget(QWidget *parent = nullptr) : QWidget(parent) {}

protected:
    void paintEvent(QPaintEvent *event) override {
        QPainter painter(this);
        painter.setPen(Qt::blue);
        painter.drawRect(rect());
        painter.drawText(10, 30, "Hello, Qt!");
    }
};

2.3. 关键点详解

2.3.1. 参数 QPaintEvent
  • QPaintEvent *event 提供了需要重绘的区域(event->rect()),但你通常不需要直接用它,除非优化重绘性能。
2.3.2. 使用 QPainter
  • 必须在 paintEvent 里创建 QPainter,并以当前 widget 为参数:QPainter painter(this);
  • QPainter 是所有绘制操作的入口,可以画线、画图、画文字等。
2.3.3. 绘制区域
  • paintEvent 只在需要时自动调用,不要手动调用,如需重绘用 update() 触发。
2.3.4. 性能注意事项
  • 因为该函数会频繁调用,如果处理慢会导致界面卡顿。

2.4. 常用绘制方法

void paintEvent(QPaintEvent *event) override {
    QPainter painter(this);

    // 绘制直线
    painter.setPen(QPen(Qt::red, 2));
    painter.drawLine(10, 10, 100, 100);

    // 绘制矩形
    painter.setBrush(Qt::green);
    painter.drawRect(120, 10, 80, 60);

    // 绘制椭圆
    painter.setBrush(Qt::yellow);
    painter.drawEllipse(220, 10, 80, 60);

    // 绘制文字
    painter.setPen(Qt::black);
    painter.drawText(10, 90, "这是文字");
}

2.5. 触发重绘

  • 主动调用 update()repaint() 可让 Qt 重新调用 paintEvent
  • update():异步请求重绘,提高效率。
  • repaint():同步立即重绘,不推荐频繁使用。
this->update();  // 请求重绘

2.6. 实用小贴士

  • 仅在 paintEvent 内使用 QPainter 绘制 widget。
  • 减少 paintEvent 内逻辑,只专注绘制。
  • QPixmap/QImage 缓存复杂绘制(双缓冲)。

2.7. 常见问题

  • 绘制不显示:未重写 paintEvent,或 QPainter 使用不当。
  • 操作 UI:不建议在 paintEvent 内更改 UI 状态,仅绘制。

2.8. 调用时机

paintEvent 由 Qt 自动调用,不手动调用。常见触发:

  • 窗口首次显示或从最小化/隐藏恢复。
  • 窗口被遮挡后暴露。
  • 主动请求:update()(异步)或 repaint()(同步)。
  • 窗口大小改变(resize)。

示意流程:

外部事件/代码
   |
   v
窗口需要重绘(如显示/恢复/遮挡/resize/update())
   |
   v
Qt 事件系统检测
   |
   v
Qt 自动调用 paintEvent
   |
   v
自定义绘制逻辑

代码演示(按钮触发重绘):

#include <QWidget>
#include <QPainter>
#include <QPushButton>

class MyWidget : public QWidget {
    Q_OBJECT
public:
    MyWidget(QWidget *parent = nullptr) : QWidget(parent) {
        auto btn = new QPushButton("重绘", this);
        btn->move(10, 10);
        connect(btn, &QPushButton::clicked, this, [this](){
            this->update(); // 触发 paintEvent
        });
    }

protected:
    void paintEvent(QPaintEvent *event) override {
        QPainter painter(this);
        painter.setPen(Qt::blue);
        painter.drawRect(rect());
        painter.drawText(10, 50, "每次重绘都会调用 paintEvent");
    }
};

3. QGraphicsItem 自定义绘制

QGraphicsItem 是 Graphics View 框架的基础元素,用于 QGraphicsScene 中的可视对象。自定义绘制通过重写 paint() 实现,与 paintEvent 不同,无事件参数。

3.1. 核心方法

必须重写:

  • QRectF boundingRect() const:返回边界矩形(重绘区域、碰撞检测)。
  • void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr):具体绘制逻辑。

3.2. 简单示例

#include <QGraphicsItem>
#include <QPainter>

class MyItem : public QGraphicsItem {
public:
    QRectF boundingRect() const override {
        return QRectF(0, 0, 100, 100);
    }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override {
        painter->setBrush(Qt::blue);
        painter->drawRect(0, 0, 100, 100);
        painter->setPen(Qt::white);
        painter->drawText(QPointF(10, 50), "Hello, GraphicsItem");
    }
};

3.3. 使用示例

#include <QGraphicsScene>
#include <QGraphicsView>
#include <QApplication>

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    QGraphicsScene scene;
    scene.setSceneRect(0, 0, 400, 300);
    MyItem *item = new MyItem();
    scene.addItem(item);
    QGraphicsView view(&scene);
    view.show();
    return a.exec();
}

3.4. 注意事项

  • boundingRect() 必须包含所有绘制内容,否则裁剪。
  • paint() 使用传入的 QPainter,类似 paintEvent 但无事件。
  • 不要手动调用 paint(),用 update() 触发。
  • setFlag() 设置交互:如 ItemIsMovable(可移动)、ItemIsSelectable(可选中)。

3.5. 进阶:响应事件

重写事件函数:

void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
    update();  // 触发重绘
}

3.6. 常见问题

  • 图形不显示boundingRect() 不足大,或 paint() 绘制错误。
  • 交互无响应:未设置相应 flag,如 setFlag(QGraphicsItem::ItemIsMovable)

4. QWidget paintEvent vs QGraphicsItem paint 对比

对比项 QWidget 的 paintEvent QGraphicsItem 的 paint
所属体系 Qt Widget(窗口部件)体系 Qt Graphics View(图形视图)体系
重绘入口函数 void paintEvent(QPaintEvent *event) void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
坐标系原点 控件左上角为 (0, 0) 图元自身的局部坐标系,左上角通常为 (0,0)
绘制对象获取 QPainter painter(this); Qt 自动传入 QPainter* painter
区域定义 由控件大小决定(rect() 必须重写 boundingRect() 明确区域
自动重绘触发 控件需要重绘时由 Qt 自动调用 图元需要重绘时由场景/视图自动调用
手动触发重绘 调用 update()repaint() 调用 update()scene()->update(item)
事件参数 QPaintEvent* event,可获取待重绘区域 无需事件参数,需用 boundingRect() 约束区域
层级/叠加 控件之间由父子关系和 Z 轴顺序管理 图元可设置 zValue() 控制叠加顺序
变换(旋转、缩放等)支持 需手动变换 QPainter,较为繁琐 支持 setRotation()setScale() 等属性
交互与事件处理 事件函数如 mousePressEvent 在 QWidget 里处理 需在 QGraphicsItem 派生类中重写相关事件函数
适用场景 常规 UI 控件自定义绘制、轻量级定制 复杂场景、动画、游戏、可视化、交互图元等

4.1. 简要说明

  • QWidgetpaintEvent 适合界面元素的自定义绘制,如按钮、面板、输入框等。
  • QGraphicsItempaint 适合需要大量图形对象、自由变换、复杂交互的场景,如图形编辑器、动画场景、拓扑图等。

4.2. 基本概述

  • QWidget::paintEvent(QPaintEvent *event):

    • 这是一个虚函数(virtual),在QWidget及其子类(如QLabelQPushButton或自定义部件)中用于处理绘制事件。
    • 当部件需要重绘时(如窗口调整大小、更新内容或外部触发update()/repaint()时),Qt会自动调用此方法。
    • 它是Qt Widgets框架的核心,用于绘制UI元素的像素级内容。你可以重载它来实现自定义绘制,但不是纯虚函数(即不需要必须实现,除非你想自定义)。
  • QGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget):

    • 这是一个纯虚函数(pure virtual),在QGraphicsItem及其子类(如QGraphicsRectItemQGraphicsEllipseItem或自定义项)中必须实现。
    • 它用于绘制图形项的外观,这些项是QGraphicsScene的一部分,由QGraphicsView视图渲染。
    • Qt Graphics View框架更适合处理大量图形元素、变换(旋转、缩放)、碰撞检测和动画,而非传统UI部件。

关键区别paintEvent是针对独立UI部件的"被动"重绘机制,而paint是针对图形场景中项的"主动"绘制接口,前者更注重部件的整体渲染,后者更注重项的局部绘制和优化。

4.3. 参数对比

  • paintEvent:

    • 参数:QPaintEvent *event – 提供重绘事件的信息,如需要重绘的区域(event->rect()event->region()),以便优化只绘制变化部分。
    • 你需要手动创建QPainter对象(通常在方法体内,如QPainter painter(this);),并在部件的坐标系中绘制。
  • paint:

    • 参数:
      • QPainter *painter – Qt提供的现成画家对象,已准备好用于绘制(无需手动创建)。
      • const QStyleOptionGraphicsItem *option – 提供项的样式、状态(如是否选中、暴露区域option->exposedRect)和变换信息,帮助实现样式一致性和优化。
      • QWidget *widget – 指向渲染该项的视图部件(通常为nullptr,除非需要特定上下文)。
    • 绘制发生在项的局部坐标系中,Qt会自动处理场景级变换。

区别paintEvent的参数更简单,焦点在事件上;paint的参数更丰富,支持高级优化和样式集成。

4.4. 调用机制和时机

  • paintEvent:

    • 由Qt的事件循环自动调用,通常在以下情况:
      • 部件首次显示。
      • 调用update()(异步重绘)或repaint()(同步重绘)。
      • 窗口事件如resize、expose、paint等。
    • 它是部件级别的,独立于其他部件。
  • paint:

    • QGraphicsView在渲染整个场景时调用(例如,当视图调用drawItems()时)。
    • 调用时机:
      • 场景更新(如项移动、添加/移除项)。
      • 视图重绘(类似paintEvent,但在场景级别)。
    • 它是场景级别的,Qt会遍历场景中的所有项,调用它们的paint,并应用变换、裁剪等优化。

区别paintEvent更像"部件自绘",而paint是"场景渲染的一部分",后者支持批量优化(如只绘制可见项)。

4.5. 实现方式和代码示例

4.5.1. QWidget paintEvent 示例
#include <QWidget>
#include <QPainter>
#include <QPaintEvent>

class CustomWidget : public QWidget {
public:
    CustomWidget(QWidget *parent = nullptr) : QWidget(parent) {}

protected:
    void paintEvent(QPaintEvent *event) override {
        QPainter painter(this);  // 创建画家,绑定到this部件
        painter.setRenderHint(QPainter::Antialiasing);  // 抗锯齿

        // 只绘制事件指定的区域以优化
        QRect rect = event->rect();
        painter.fillRect(rect, Qt::white);  // 背景

        // 绘制一个红色矩形
        painter.setPen(Qt::red);
        painter.drawRect(10, 10, width() - 20, height() - 20);
    }
};

// 使用示例(在main中)
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    CustomWidget widget;
    widget.resize(200, 200);
    widget.show();
    return app.exec();
}
4.5.2. QGraphicsItem paint 示例
#include <QGraphicsItem>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QApplication>

class CustomGraphicsItem : public QGraphicsItem {
public:
    CustomGraphicsItem() {
        setFlag(QGraphicsItem::ItemIsMovable);  // 可移动
    }

    QRectF boundingRect() const override {
        return QRectF(0, 0, 100, 100);  // 定义项的边界矩形(必须实现)
    }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override {
        Q_UNUSED(widget);  // 通常忽略widget

        painter->setRenderHint(QPainter::Antialiasing);

        // 使用option优化:只绘制暴露区域
        QRectF exposed = option->exposedRect;
        painter->fillRect(exposed, Qt::white);  // 背景

        // 绘制一个红色矩形(在项局部坐标系)
        painter->setPen(Qt::red);
        painter->drawRect(boundingRect().adjusted(10, 10, -10, -10));

        // 如果项被选中,绘制高亮(使用option的状态)
        if (option->state & QStyle::State_Selected) {
            painter->setPen(Qt::blue);
            painter->drawRect(boundingRect());
        }
    }
};

// 使用示例(在main中)
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QGraphicsScene scene;
    CustomGraphicsItem *item = new CustomGraphicsItem();
    scene.addItem(item);

    QGraphicsView view(&scene);
    view.resize(200, 200);
    view.show();
    return app.exec();
}

代码区别

  • paintEvent中,你需要创建QPainter并处理整个部件。
  • paint中,QPainter已提供,你必须实现boundingRect()来定义项的边界(用于碰撞和渲染优化),并可利用option处理状态(如选中)。
  • QGraphicsItem需要与QGraphicsSceneQGraphicsView结合使用,而QWidget可以独立。

4.6. 性能和适用场景

  • paintEvent:

    • 适合简单UI(如按钮、面板),性能依赖于部件数量。大量重叠部件可能导致低效重绘。
    • 优化:使用event->rect()避免绘制整个区域。
  • paint:

    • 适合复杂图形(如游戏、图表、动画),Graphics View有内置优化(如索引、裁剪、变换缓存),处理数千项时更高效。
    • 优化:使用option->exposedRectboundingRect()减少不必要绘制;支持OpenGL渲染加速。

区别:对于高性能图形,QGraphicsItem::paint更优;对于标准UI,QWidget::paintEvent更直接。

4.7. 优缺点总结

  • 相似点:两者都使用QPainter绘制,支持相同绘图API(如drawRect、drawText)。
  • QWidget::paintEvent 的优点:简单、直接集成到UI;缺点:不适合复杂变换或大量元素。
  • QGraphicsItem::paint 的优点:支持高级功能(如动画、组、效果);缺点:必须实现更多方法(如boundingRect),学习曲线稍陡。
  • 选择建议:如果构建传统窗口UI,用QWidget;如果需要可缩放/可变换的图形场景,用QGraphicsItem。Qt允许混合使用(如在QGraphicsView中嵌入QWidget)。

5. 自定义 QGraphicsRectItem

QGraphicsRectItem 是矩形专用类,继承 QGraphicsItem,内置矩形管理(如 rect()setRect())。

5.1. 基础模板

#include <QGraphicsRectItem>
#include <QPen>
#include <QBrush>
#include <QGraphicsSceneMouseEvent>

class MyRectItem : public QGraphicsRectItem
{
public:
    // 构造函数
    MyRectItem(const QRectF &rect) : QGraphicsRectItem(rect) {
        setFlag(QGraphicsItem::ItemIsSelectable, true);  // 可选中
        setFlag(QGraphicsItem::ItemIsMovable, true);     // 可移动
    }

    // 重写 paint 方法,实现自定义绘制
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override
    {
        // 1. 绘制矩形
        if (isSelected())
            painter->setPen(QPen(Qt::red, 2));  // 选中时红色边框
        else
            painter->setPen(QPen(Qt::black, 1));
        painter->setBrush(QBrush(Qt::cyan));
        painter->drawRect(rect());

        // 2. 可自定义其他内容
        painter->setPen(Qt::blue);
        painter->drawText(rect().center(), "RectItem");
    }

    // 可选:自定义交互
    void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override
    {
        // 双击变色
        setBrush(QBrush(Qt::yellow));
        update();
        QGraphicsRectItem::mouseDoubleClickEvent(event); // 保持基类行为
    }
};

5.2. 使用示例

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QGraphicsScene scene(0, 0, 400, 300);
    MyRectItem *rectItem = new MyRectItem(QRectF(50, 50, 120, 80));
    scene.addItem(rectItem);
    QGraphicsView view(&scene);
    view.show();
    return app.exec();
}

5.3. 常用 Flags

  • ItemIsMovable:可拖动。
  • ItemIsSelectable:可选中。
  • ItemSendsGeometryChanges:几何变化通知。

5.4. 常见扩展

  • 变形/缩放/旋转:用 setScale()setRotation() 或重写事件。
  • 外观自定义:在 paint() 根据状态绘制。
  • 交互提示:重写 hoverEnterEvent/hoverLeaveEvent

6. 自定义 QGraphicsItem vs QGraphicsRectItem 对比

对比项 自定义 QGraphicsItem 自定义 QGraphicsRectItem
继承基类 QGraphicsItem QGraphicsRectItem(继承 QGraphicsItem)
适合场景 任意形状(如多边形、圆) 矩形形状(如块、框选)
边界定义 重写 boundingRect() 构造函数指定矩形
绘制方法 重写 paint(),全实现 可重写 paint(),复用父类
矩形属性 自行维护成员变量 内置 rect()setRect()
几何变换 自行实现或调用基类 继承支持
交互扩展 全自定义事件 继承父类事件,可扩展
使用便捷性 灵活但代码多 易用,适合矩形需求
性能 取决于自定义 内置优化,适合大量矩形
例子 自定义图标、曲线 选择框、色块、条状图

6.1. 说明

  • QGraphicsItem:全自定义,像白纸,从零定义边界、绘制、属性。适合非标准形状。
  • QGraphicsRectItem:预制矩形模板,复用 Qt 逻辑,专注扩展。适合矩形相关。

6.2. QGraphicsItem 示例

#include <QGraphicsItem>
#include <QPainter>
#include <QStyleOptionGraphicsItem>

class MyCustomItem : public QGraphicsItem {
private:
    QRectF m_rect;  // 自己维护矩形数据

public:
    MyCustomItem(QRectF rect) : m_rect(rect) {
        setFlag(QGraphicsItem::ItemIsSelectable, true);  // 手动设置选中
    }

    // 必须:定义边界(包含所有绘制内容)
    QRectF boundingRect() const override {
        return m_rect;  // 用自己的 rect
    }

    // 必须:全自定义绘制
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override {
        Q_UNUSED(option); Q_UNUSED(widget);  // 忽略参数

        if (isSelected()) {
            painter->setPen(QPen(Qt::red, 2));  // 选中高亮,自行判断
        } else {
            painter->setPen(QPen(Qt::black, 1));
        }
        painter->setBrush(Qt::blue);
        painter->drawRect(m_rect);  // 手动绘制矩形

        // 可加文字等,自行实现
        painter->drawText(m_rect.center(), "Custom Item");
    }

    // 如果要改大小,自行加方法
    void setRect(const QRectF &rect) { m_rect = rect; update(); }
};

6.3. QGraphicsRectItem 示例

#include <QGraphicsRectItem>
#include <QPainter>
#include <QStyleOptionGraphicsItem>

class MyRectItem : public QGraphicsRectItem {
public:
    MyRectItem(QRectF rect) : QGraphicsRectItem(rect) {  // 构造函数直接传 rect,内置管理
        setFlag(QGraphicsItem::ItemIsSelectable, true);  // 继承设置
    }

    // 可选:扩展绘制(父类已有 drawRect,可覆盖加内容)
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override {
        // 调用父类绘制矩形(复用!)
        QGraphicsRectItem::paint(painter, option, widget);

        // 扩展:加高亮和文字
        if (isSelected()) {
            painter->setPen(QPen(Qt::red, 2));  // 覆盖边框
            painter->drawRect(rect());  // 用内置 rect()
        }
        painter->setPen(Qt::white);
        painter->drawText(rect().center(), "Rect Item");  // 内置 center()
    }

    // 大小改用内置
    // 无需额外方法,setRect() 已继承
};

6.4. 代码对比要点

  • QGraphicsItem:需私有机 rect 变量、boundingRect() 返回它、手动 drawRect(m_rect)。改大小需自写 setter。
  • QGraphicsRectItem:构造函数传 rect 就行、paint() 可调用父类复用绘制、用 rect() 直接访问。改大小用 setRect() 无缝。
  • 共同:都用 paint() 绘制,都支持 flag(如选中)。

6.5. 使用场景对比

两者使用相同(加到 QGraphicsScene),但 QGraphicsRectItem 更省事:

// 通用使用
QGraphicsScene scene(0, 0, 400, 300);
MyCustomItem *custom = new MyCustomItem(QRectF(50, 50, 100, 100));  // 传 rect
// 或 MyRectItem *rect = new MyRectItem(QRectF(50, 50, 100, 100));
scene.addItem(custom);  // 或 rect
QGraphicsView view(&scene); view.show();

6.6. 优缺点对比

维度 QGraphicsItem (全自定义) QGraphicsRectItem (矩形扩展)
灵活性 最高:任意形状(如圆、多边形、路径) 中等:限于矩形,但易扩展(如渐变矩形)
代码量 多:边界、属性、绘制全写 少:复用父类,专注业务逻辑
维护性 低:自己管一切,易出错(如边界漏内容) 高:Qt 管矩形细节,少 bug
性能 好(自定义优化),但需手动 更好(内置形状优化,如碰撞检测快)
缺点 起步陡,适合专家 不适合非矩形(如想画圆,得用 QGraphicsEllipseItem)

6.7. 适用场景区别

  • 选 QGraphicsItem

    • 需要非标准形状:如自定义图标、心形、曲线图、游戏精灵。
    • 完全控制:如 3D 投影或复杂路径绘制。
    • 示例:图形编辑器中的自由绘图工具。
  • 选 QGraphicsRectItem

    • 矩形相关:进度条、按钮块、网格单元、选择框、色块。
    • 快速原型:UI 面板、数据可视化矩形(如柱状图条)。
    • 示例:流程图节点、地图区域。

选择建议:先问“这是矩形吗?”如果是,用 QGraphicsRectItem(简单);否则,用 QGraphicsItem(强大)。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐