qt-C++语法笔记之Qt Graphics View 框架中的类型辨析完全指南

code review!
文章目录
- [qt-C++语法笔记之Qt Graphics View 框架中的类型辨析完全指南](#qt-C++语法笔记之Qt Graphics View 框架中的类型辨析完全指南)
-
- 一、核心概念概览
-
- [1.1 总览大表](#1.1 总览大表)
- 二、先说结论
-
- [2.1 `QLine`、`QRect`、`QPainterPath` 是"数据",不是"图元"](#2.1
QLine、QRect、QPainterPath是"数据",不是"图元") - [2.2 `QGraphicsLineItem`、`QGraphicsRectItem`、`QGraphicsPathItem` 都能直接构造实例](#2.2
QGraphicsLineItem、QGraphicsRectItem、QGraphicsPathItem都能直接构造实例) - [2.3 `QGraphicsItem` 不是"现成能画东西的具体图元"](#2.3
QGraphicsItem不是"现成能画东西的具体图元") -
- [2.3.1 `QGraphicsItem` 的特点](#2.3.1
QGraphicsItem的特点)
- [2.3.1 `QGraphicsItem` 的特点](#2.3.1
- [2.4 `QAbstractGraphicsShapeItem` 不能直接实例化](#2.4
QAbstractGraphicsShapeItem不能直接实例化)
- [2.1 `QLine`、`QRect`、`QPainterPath` 是"数据",不是"图元"](#2.1
- 三、继承关系辨析
-
- [3.1 继承关系总览表](#3.1 继承关系总览表)
- [3.2 完整层级关系表](#3.2 完整层级关系表)
- [3.3 继承脉络表](#3.3 继承脉络表)
- 四、关键设计问题解析
-
- [4.1 为什么 `QGraphicsItem` 不能像 `QGraphicsRectItem` 一样直接用?](#4.1 为什么
QGraphicsItem不能像QGraphicsRectItem一样直接用?) - [4.2 为什么 `QAbstractGraphicsShapeItem` 也不能直接用?](#4.2 为什么
QAbstractGraphicsShapeItem也不能直接用?)
- [4.1 为什么 `QGraphicsItem` 不能像 `QGraphicsRectItem` 一样直接用?](#4.1 为什么
- 五、实战选择指南
-
- [5.1 什么时候"直接用现成图元",什么时候"自定义"?](#5.1 什么时候"直接用现成图元",什么时候"自定义"?)
-
- [5.1.1 适合直接用 `QGraphicsLineItem / RectItem / PathItem`](#5.1.1 适合直接用
QGraphicsLineItem / RectItem / PathItem) - [5.1.2 适合继承 `QGraphicsLineItem / RectItem / PathItem`](#5.1.2 适合继承
QGraphicsLineItem / RectItem / PathItem) - [5.1.3 适合继承 `QGraphicsItem`](#5.1.3 适合继承
QGraphicsItem) - [5.1.4 适合继承 `QAbstractGraphicsShapeItem`](#5.1.4 适合继承
QAbstractGraphicsShapeItem)
- [5.1.1 适合直接用 `QGraphicsLineItem / RectItem / PathItem`](#5.1.1 适合直接用
- [5.2 选择决策表](#5.2 选择决策表)
- 六、最常见的混淆点
-
- [6.1 混淆 1:`QRect` 和 `QGraphicsRectItem` 不是一个层次](#6.1 混淆 1:
QRect和QGraphicsRectItem不是一个层次) - [6.2 混淆 2:`QPainterPath` 和 `QGraphicsPathItem` 不是一个层次](#6.2 混淆 2:
QPainterPath和QGraphicsPathItem不是一个层次) - [6.3 混淆 3:`QGraphicsItem` 是"接口框架",不是"现成图形"](#6.3 混淆 3:
QGraphicsItem是"接口框架",不是"现成图形")
- [6.1 混淆 1:`QRect` 和 `QGraphicsRectItem` 不是一个层次](#6.1 混淆 1:
- 七、简化记忆法
-
- [7.1 数据 vs 图元](#7.1 数据 vs 图元)
- [7.2 现成图元 vs 抽象基类](#7.2 现成图元 vs 抽象基类)
- [7.3 用一句话概括每个类](#7.3 用一句话概括每个类)
- 八、常见判断与结论
-
- [8.1 各类能否直接构造实例?](#8.1 各类能否直接构造实例?)
- [8.2 关键结论汇总](#8.2 关键结论汇总)
- 九、自定义图元实现指南
-
- [9.1 三种自定义方式横向对比](#9.1 三种自定义方式横向对比)
- [9.2 最小可用自定义代码对比](#9.2 最小可用自定义代码对比)
-
- [9.2.1 继承 `QGraphicsRectItem`](#9.2.1 继承
QGraphicsRectItem) - [9.2.2 继承 `QAbstractGraphicsShapeItem`](#9.2.2 继承
QAbstractGraphicsShapeItem) - [9.2.3 继承 `QGraphicsItem`](#9.2.3 继承
QGraphicsItem)
- [9.2.1 继承 `QGraphicsRectItem`](#9.2.1 继承
- [9.3 自定义 `QGraphicsItem` 时必须知道的核心函数](#9.3 自定义
QGraphicsItem时必须知道的核心函数) - [9.4 `boundingRect()`、`shape()`、实际绘制范围 三者区别](#9.4
boundingRect()、shape()、实际绘制范围 三者区别) -
- [9.4.1 例子](#9.4.1 例子)
- [十、`QPainterPath` 的特殊作用](#十、
QPainterPath的特殊作用) -
- [10.1 `QPainterPath` 在自定义图元里特别重要](#10.1
QPainterPath在自定义图元里特别重要) -
- [10.1.1 示例代码](#10.1.1 示例代码)
- [10.1 `QPainterPath` 在自定义图元里特别重要](#10.1
- 十一、常见图元类型与对应类
- 总结与快速查询
一、核心概念概览
下面用一个大表格把这些概念放在一起进行辨析。Qt Graphics View 框架中分为两条主要线:
- 几何数据类 :
QLine/QRect/QPainterPath - 图元对象类 :
QGraphicsLineItem/QGraphicsRectItem/QGraphicsPathItem/QGraphicsItem/QAbstractGraphicsShapeItem
1.1 总览大表
| 类名 | 类别 | 表示的本质 | 能否直接实例化 | 是否可直接加入 QGraphicsScene |
是否自带绘制实现 | 是否适合作为自定义基类 | 典型用途 | 备注 |
|---|---|---|---|---|---|---|---|---|
QLine / QLineF |
几何数据类 | 一条线段的数据 | 可以 | 不可以 | 否 | 否 | 保存线段坐标 | 只是"数据",不是图元 |
QRect / QRectF |
几何数据类 | 一个矩形区域的数据 | 可以 | 不可以 | 否 | 否 | 保存矩形范围 | 也是纯数据 |
QPainterPath |
几何数据类 | 一条路径的数据集合 | 可以 | 不可以 | 否 | 否 | 复杂轮廓、曲线、命中区域 | 常用于自定义绘制和 shape() |
QGraphicsLineItem |
图元类 | 基于线段的现成图元 | 可以 | 可以 | 有 | 可以 | 直接画线,或继承扩展 | 内部通常持有 QLineF |
QGraphicsRectItem |
图元类 | 基于矩形的现成图元 | 可以 | 可以 | 有 | 可以 | 直接画矩形,或继承扩展 | 内部通常持有 QRectF |
QGraphicsPathItem |
图元类 | 基于路径的现成图元 | 可以 | 可以 | 有 | 可以 | 直接画复杂路径,或继承扩展 | 内部通常持有 QPainterPath |
自定义 QGraphicsLineItem 子类 |
图元类 | 在线图元基础上扩展行为 | 可以 | 可以 | 继承父类已有实现 | 可以 | 给线增加交互、业务逻辑 | 若只扩展行为,这是很方便的选择 |
自定义 QGraphicsRectItem 子类 |
图元类 | 在矩形图元基础上扩展行为 | 可以 | 可以 | 继承父类已有实现 | 可以 | 可拖动矩形、选择框等 | 常用于节点框、包围框 |
自定义 QGraphicsPathItem 子类 |
图元类 | 在路径图元基础上扩展行为 | 可以 | 可以 | 继承父类已有实现 | 可以 | 连线、箭头、复杂形状 | 自定义连线常见 |
QGraphicsItem |
图元抽象基类 | 所有图元的最基础接口 | 不能直接正常使用为具体图元 | 理论上子类可以,自己本身不适合直接用 | 没有完整具体图形实现 | 非常适合 | 完全自定义图元 | 需重写至少 boundingRect() 和 paint() |
QAbstractGraphicsShapeItem |
图元抽象中间基类 | 带 pen/brush 属性的形状图元基类 |
不能直接实例化 | 不可以 | 不提供完整具体形状 | 适合 | 自定义"有轮廓/填充"的图元 | 它是抽象类 |
自定义 QGraphicsItem 子类 |
图元类 | 完全自主定义外观和行为 | 可以 | 可以 | 由你自己实现 | 是最终常见方案 | 不规则图元、组合逻辑、复杂交互 | 灵活度最高 |
二、先说结论
2.1 QLine、QRect、QPainterPath 是"数据",不是"图元"
它们都能直接构造实例,例如:
cpp
QLineF line(0, 0, 100, 100);
QRectF rect(0, 0, 80, 60);
QPainterPath path;
path.moveTo(0, 0);
path.lineTo(100, 50);
但它们不能直接放进 QGraphicsScene ,因为它们不是 QGraphicsItem。
2.2 QGraphicsLineItem、QGraphicsRectItem、QGraphicsPathItem 都能直接构造实例
例如:
cpp
scene->addItem(new QGraphicsLineItem(QLineF(0, 0, 100, 100)));
scene->addItem(new QGraphicsRectItem(QRectF(0, 0, 80, 60)));
scene->addItem(new QGraphicsPathItem(path));
它们是 Qt 已经帮你写好的具体图元类,能直接显示。
同时你也可以继承它们来自定义:
cpp
class MyLineItem : public QGraphicsLineItem
{
public:
using QGraphicsLineItem::QGraphicsLineItem;
};
这类继承适合:
- 已经是线/矩形/路径
- 只想增加事件处理、状态、业务属性
- 不想从零重写绘制逻辑
2.3 QGraphicsItem 不是"现成能画东西的具体图元"
2.3.1 QGraphicsItem 的特点
更准确地说:
- 它是基类
- 通常不会直接拿来实例化使用
- 因为你必须自己定义图元边界和绘制方式
在实际开发语义里,可以理解为:
不能直接作为一个可用的具体图元来构造使用,必须继承它并实现关键虚函数。
典型自定义写法:
cpp
class MyItem : public QGraphicsItem
{
public:
QRectF boundingRect() const override
{
return QRectF(0, 0, 100, 50);
}
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget = nullptr) override
{
painter->setBrush(Qt::yellow);
painter->drawEllipse(boundingRect());
}
};
然后:
cpp
scene->addItem(new MyItem);
2.4 QAbstractGraphicsShapeItem 不能直接实例化
这个可以比较明确地说:
- 它是抽象类
- 主要作用是给子类提供公共的
pen/brush管理 - 它本身不代表某个具体形状
- 所以不能直接构造实例
它的典型子类包括:
QGraphicsRectItemQGraphicsEllipseItemQGraphicsPathItemQGraphicsPolygonItemQGraphicsSimpleTextItem不是它的子类QGraphicsLineItem也不是它的子类
这一点很容易混:
| 类 | 是否继承自 QAbstractGraphicsShapeItem |
|---|---|
QGraphicsRectItem |
是 |
QGraphicsPathItem |
是 |
QGraphicsEllipseItem |
是 |
QGraphicsPolygonItem |
是 |
QGraphicsLineItem |
否 |
因为线通常只有 pen,没有封闭填充区域,所以它没有走 "shape item" 这套抽象。
三、继承关系辨析
3.1 继承关系总览表
| 类 | 直接/间接基类关系 | 是否抽象 | 说明 |
|---|---|---|---|
QGraphicsItem |
基础根类 | 是抽象基类语义 | 所有图元的核心接口 |
QAbstractGraphicsShapeItem |
继承 QGraphicsItem |
是 | 为"有画笔/画刷"的形状图元提供公共能力 |
QGraphicsLineItem |
继承 QGraphicsItem |
否 | 具体线图元 |
QGraphicsRectItem |
继承 QAbstractGraphicsShapeItem |
否 | 具体矩形图元 |
QGraphicsPathItem |
继承 QAbstractGraphicsShapeItem |
否 | 具体路径图元 |
3.2 完整层级关系表
| 层级 | 类名 | 是什么 | 是否抽象 | 能否直接 new 后作为图元使用 |
你通常要不要继承它 | 典型职责 |
|---|---|---|---|---|---|---|
| 几何数据层 | QLineF |
线段数据 | 否 | 否 | 否 | 保存线段几何信息 |
| 几何数据层 | QRectF |
矩形数据 | 否 | 否 | 否 | 保存矩形范围 |
| 几何数据层 | QPainterPath |
路径数据 | 否 | 否 | 否 | 保存复杂路径、曲线、轮廓 |
| 图元抽象层 | QGraphicsItem |
所有图元共同基类 | 是 | 否 | 经常 | 定义图元的坐标、绘制、事件、碰撞等接口 |
| 图元抽象层 | QAbstractGraphicsShapeItem |
形状图元抽象基类 | 是 | 否 | 有时 | 在 QGraphicsItem 基础上补充 pen/brush |
| 图元具体实现层 | QGraphicsLineItem |
线图元 | 否 | 可以 | 可以 | 直接显示线段 |
| 图元具体实现层 | QGraphicsRectItem |
矩形图元 | 否 | 可以 | 可以 | 直接显示矩形 |
| 图元具体实现层 | QGraphicsPathItem |
路径图元 | 否 | 可以 | 可以 | 直接显示路径 |
| 图元具体实现层 | 自定义 QGraphicsItem |
完全自定义图元 | 否 | 可以 | 这是结果类 | 实现任意外观与交互 |
| 图元具体实现层 | 自定义 QAbstractGraphicsShapeItem |
自定义形状图元 | 否 | 可以 | 这是结果类 | 实现带轮廓/填充的自定义图形 |
| 图元具体实现层 | 自定义 QGraphicsRectItem 等 |
在现成图元上扩展 | 否 | 可以 | 这是结果类 | 在已有绘制基础上加逻辑 |
3.3 继承脉络表
下面是你最关心的继承脉络。
| 类 | 父类 | 是否属于"具体图元" | 自带 paint() 具体实现 |
自带 boundingRect() 具体实现 |
备注 |
|---|---|---|---|---|---|
QGraphicsItem |
无(核心基类) | 否 | 否 | 否 | 最底层抽象接口 |
QAbstractGraphicsShapeItem |
QGraphicsItem |
否 | 否 | 否 | 只增加 pen/brush 这类公共能力 |
QGraphicsLineItem |
QGraphicsItem |
是 | 是 | 是 | 线不是"填充形状",所以不走 QAbstractGraphicsShapeItem |
QGraphicsRectItem |
QAbstractGraphicsShapeItem |
是 | 是 | 是 | 典型 shape item |
QGraphicsPathItem |
QAbstractGraphicsShapeItem |
是 | 是 | 是 | 典型 shape item |
可以把它理解成:
text
QGraphicsItem
├─ QAbstractGraphicsShapeItem
│ ├─ QGraphicsRectItem
│ ├─ QGraphicsEllipseItem
│ ├─ QGraphicsPathItem
│ └─ QGraphicsPolygonItem
└─ QGraphicsLineItem
四、关键设计问题解析
4.1 为什么 QGraphicsItem 不能像 QGraphicsRectItem 一样直接用?
因为 QGraphicsItem 只定义了"图元应该具备什么能力",但没有定义你到底长什么样。
一个图元至少要回答两个问题:
-
你占多大区域?
对应
boundingRect() -
你怎么画出来?
对应
paint()
而 QGraphicsRectItem 已经替你回答了:
- 区域就是它的矩形
- 绘制就是用 pen/brush 画矩形
但 QGraphicsItem 不知道你想画:
- 圆
- 箭头
- 节点
- 多边形
- 流程图块
- 自定义控件样式图元
所以它必须让你自己实现。
4.2 为什么 QAbstractGraphicsShapeItem 也不能直接用?
因为它只是一个**"半成品抽象层"**。
它帮你准备了这类通用属性:
setPen(...)pen()setBrush(...)brush()
但它仍然不知道你到底是:
- 矩形
- 椭圆
- 路径
- 多边形
- 圆角气泡框
所以它也不能单独拿来作为完整图元使用。
你可以理解为:
| 类 | 提供了什么 | 没提供什么 |
|---|---|---|
QGraphicsItem |
图元框架 | 图形外观 |
QAbstractGraphicsShapeItem |
图元框架 + pen/brush | 具体形状 |
五、实战选择指南
5.1 什么时候"直接用现成图元",什么时候"自定义"?
5.1.1 适合直接用 QGraphicsLineItem / RectItem / PathItem
如果你的需求只是:
- 画一条线
- 画一个矩形
- 画一条路径
- 设置颜色、线宽、选中、移动
那么直接用现成类就够了:
cpp
auto *item = new QGraphicsRectItem(0, 0, 100, 60);
item->setBrush(Qt::green);
item->setPen(QPen(Qt::black, 2));
scene->addItem(item);
5.1.2 适合继承 QGraphicsLineItem / RectItem / PathItem
如果你只是想在现成图元上加一点行为,比如:
- 鼠标点击响应
- hover 高亮
- 保存业务 id
- 连线端点逻辑
- 选中后显示控制点
那么继承现成图元最省事:
cpp
class MyRectItem : public QGraphicsRectItem
{
public:
MyRectItem(const QRectF &rect) : QGraphicsRectItem(rect) {}
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override
{
setBrush(Qt::red);
QGraphicsRectItem::mousePressEvent(event);
}
};
5.1.3 适合继承 QGraphicsItem
如果你需要:
- 非规则图形
- 一个图元里画很多内容
- 自定义命中区域
- 特殊坐标逻辑
- 完全控制绘制过程
那么直接继承 QGraphicsItem 最灵活。
例如一个节点图元,同时画:
- 圆角矩形背景
- 标题文字
- 图标
- 输入输出端口
这时继承 QGraphicsRectItem 反而不自然,通常会直接继承 QGraphicsItem。
5.1.4 适合继承 QAbstractGraphicsShapeItem
如果你的自定义图元本质上是一个"形状":
- 有轮廓
pen - 有填充
brush - 你希望复用 shape item 的通用属性管理
那么可以继承它。
比如:
cpp
class MyShapeItem : public QAbstractGraphicsShapeItem
{
public:
QRectF boundingRect() const override
{
return QRectF(0, 0, 100, 100);
}
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *,
QWidget *) override
{
painter->setPen(pen());
painter->setBrush(brush());
painter->drawRoundedRect(boundingRect(), 10, 10);
}
};
这里 pen() 和 brush() 就是从 QAbstractGraphicsShapeItem 继承来的便利接口。
5.2 选择决策表
这是开发中最实用的一张表。
| 需求场景 | 推荐继承类 | 原因 | 举例 |
|---|---|---|---|
| 就想画一条普通线 | 直接用 QGraphicsLineItem |
现成可用 | 辅助线、边框线 |
| 就想画一个普通矩形 | 直接用 QGraphicsRectItem |
现成可用 | 选区框、节点框 |
| 就想画复杂路径 | 直接用 QGraphicsPathItem |
现成可用 | 曲线、轮廓、连接线 |
| 图形已经是线/矩形/路径,只想加点击、拖动、hover、业务数据 | 继承对应现成类 | 省事,复用现成绘制和边界计算 | 自定义连线、自定义节点框 |
| 图形本质是"带填充和边框的自定义形状" | 继承 QAbstractGraphicsShapeItem |
复用 pen/brush 体系 |
圆角框、气泡框、自定义卡片 |
| 图形完全不规则,或者一个 item 里画很多元素 | 继承 QGraphicsItem |
灵活度最高 | 流程图节点、拓扑节点、带端口组件 |
| 命中区域、边界区域、绘制都很特殊 | 继承 QGraphicsItem |
完全自己控制 | 箭头、可编辑贝塞尔边、复杂交互组件 |
六、最常见的混淆点
6.1 混淆 1:QRect 和 QGraphicsRectItem 不是一个层次
| 名称 | 含义 |
|---|---|
QRectF |
只是矩形数据 |
QGraphicsRectItem |
可以显示在场景里的矩形图元 |
可以理解成:
QRectF= "矩形参数"QGraphicsRectItem= "拿这个参数画出来的图元对象"
6.2 混淆 2:QPainterPath 和 QGraphicsPathItem 不是一个层次
| 名称 | 含义 |
|---|---|
QPainterPath |
路径数据 |
QGraphicsPathItem |
显示该路径的图元 |
6.3 混淆 3:QGraphicsItem 是"接口框架",不是"现成图形"
它更像"所有图元的父类规范"。
它要求你告诉 Qt:
- 我占多大范围:
boundingRect() - 我怎么画:
paint()
不提供现成几何形状。
七、简化记忆法
7.1 数据 vs 图元
| 数据类 | 图元类 |
|---|---|
QLineF |
QGraphicsLineItem |
QRectF |
QGraphicsRectItem |
QPainterPath |
QGraphicsPathItem |
即:
前者是"描述图形的数据"
后者是"场景中真正显示的对象"
7.2 现成图元 vs 抽象基类
| 现成图元 | 抽象基类 |
|---|---|
QGraphicsLineItem |
QGraphicsItem |
QGraphicsRectItem |
QAbstractGraphicsShapeItem |
QGraphicsPathItem |
QAbstractGraphicsShapeItem |
即:
名字里带具体图形名的,往往是可以直接用的
名字偏"抽象概念"的,往往是给你继承的
7.3 用一句话概括每个类
| 类 | 一句话概括 |
|---|---|
QLineF |
一条线段的数据 |
QRectF |
一个矩形的数据 |
QPainterPath |
一组路径/曲线的数据 |
QGraphicsLineItem |
Qt 内置的"线图元" |
QGraphicsRectItem |
Qt 内置的"矩形图元" |
QGraphicsPathItem |
Qt 内置的"路径图元" |
QGraphicsItem |
所有图元的抽象父类,适合完全自定义 |
QAbstractGraphicsShapeItem |
带 pen/brush 支持的形状图元抽象基类 |
八、常见判断与结论
8.1 各类能否直接构造实例?
| 问题 | 答案 | 说明 |
|---|---|---|
QLine、QRect、QPainterPath 能直接构造实例吗? |
能 | 但它们只是数据类,不是场景图元 |
QGraphicsLineItem、QGraphicsRectItem、QGraphicsPathItem 能直接构造实例吗? |
能 | 它们是具体图元类,可以直接 new 后加入 QGraphicsScene |
自定义 QGraphicsLineItem、QGraphicsRectItem、QGraphicsPathItem 可以吗? |
可以 | 这很常见,尤其是给现成图元增加交互和业务逻辑时 |
QGraphicsItem 和 QAbstractGraphicsShapeItem 都不能直接构造实例吗? |
是的 | QAbstractGraphicsShapeItem 是抽象类;QGraphicsItem 本质上也不是拿来直接当具体图元用的 |
自定义 QGraphicsItem 可以吗? |
当然可以 | 这是最常见的完全自定义方案 |
8.2 关键结论汇总
| 说法 | 对错 | 说明 |
|---|---|---|
QLineF、QRectF、QPainterPath 可以直接构造实例 |
对 | 但它们只是数据类 |
QGraphicsLineItem、QGraphicsRectItem、QGraphicsPathItem 可以直接构造实例 |
对 | 它们是具体图元 |
可以自定义继承 QGraphicsLineItem、QGraphicsRectItem、QGraphicsPathItem |
对 | 常用于加交互或业务逻辑 |
QAbstractGraphicsShapeItem 不能直接构造实例 |
对 | 它是抽象类 |
QGraphicsItem 不能直接当作现成图元来用 |
对 | 必须实现关键虚函数后作为子类使用 |
自定义 QGraphicsItem 是最灵活方案 |
对 | 但工作量最大 |
九、自定义图元实现指南
9.1 三种自定义方式横向对比
| 自定义方式 | 基类 | 开发成本 | 灵活度 | 可复用 Qt 现成绘制 | 推荐场景 |
|---|---|---|---|---|---|
继承 QGraphicsLineItem/RectItem/PathItem |
具体图元类 | 低 | 中 | 高 | 改行为,不大改外观 |
继承 QAbstractGraphicsShapeItem |
抽象形状基类 | 中 | 中高 | 中 | 自定义形状,但仍然想用 pen/brush |
继承 QGraphicsItem |
最基础抽象类 | 高 | 最高 | 低 | 完全自定义复杂图元 |
9.2 最小可用自定义代码对比
9.2.1 继承 QGraphicsRectItem
适合:本质上就是矩形,只加一点行为。
cpp
class MyRectItem : public QGraphicsRectItem
{
public:
explicit MyRectItem(const QRectF &rect, QGraphicsItem *parent = nullptr)
: QGraphicsRectItem(rect, parent)
{
setBrush(Qt::yellow);
setPen(QPen(Qt::black, 2));
setFlag(QGraphicsItem::ItemIsSelectable);
setFlag(QGraphicsItem::ItemIsMovable);
}
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override
{
setBrush(Qt::red);
QGraphicsRectItem::mousePressEvent(event);
}
};
特点
- 不用自己写
boundingRect() - 不用自己写
paint() - 改行为最方便
9.2.2 继承 QAbstractGraphicsShapeItem
适合:是"形状",但不是 Qt 现成那几种形状。
cpp
class MyShapeItem : public QAbstractGraphicsShapeItem
{
public:
explicit MyShapeItem(QGraphicsItem *parent = nullptr)
: QAbstractGraphicsShapeItem(parent)
{
setPen(QPen(Qt::blue, 2));
setBrush(QColor(200, 220, 255));
}
QRectF boundingRect() const override
{
return QRectF(0, 0, 120, 80);
}
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *,
QWidget *) override
{
painter->setPen(pen());
painter->setBrush(brush());
painter->drawRoundedRect(boundingRect(), 12, 12);
}
};
特点
- 你要自己画
- 但
pen/brush不用自己维护字段 - 比
QGraphicsItem省一点
9.2.3 继承 QGraphicsItem
适合:完全自定义。
cpp
class MyItem : public QGraphicsItem
{
public:
explicit MyItem(QGraphicsItem *parent = nullptr)
: QGraphicsItem(parent)
{
setFlag(ItemIsMovable);
setFlag(ItemIsSelectable);
}
QRectF boundingRect() const override
{
return QRectF(0, 0, 140, 90);
}
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget = nullptr) override
{
Q_UNUSED(option)
Q_UNUSED(widget)
painter->setPen(QPen(Qt::black, 2));
painter->setBrush(QColor(240, 240, 180));
painter->drawRoundedRect(0, 0, 140, 90, 10, 10);
painter->setPen(Qt::black);
painter->drawText(QRectF(0, 0, 140, 90), Qt::AlignCenter, "My Item");
}
};
特点
- 最自由
- 也最需要你自己负责
9.3 自定义 QGraphicsItem 时必须知道的核心函数
| 函数 | 是否常常必须重写 | 作用 | 不重写会怎样 |
|---|---|---|---|
boundingRect() |
必须 | 告诉场景此图元占据的外接矩形 | 无法正确重绘、碰撞、更新 |
paint() |
必须 | 真正绘制图元内容 | 图元不会被正确画出 |
shape() |
常用但非必须 | 定义更精确的命中/碰撞区域 | 默认通常只按外接矩形或粗略区域处理 |
contains() |
有时 | 判断某点是否在图元内 | 通常依赖 shape() |
itemChange() |
常用 | 监听位置、选择状态等变化 | 无法优雅响应属性变化 |
mousePressEvent() |
常用 | 鼠标按下处理 | 使用默认行为 |
mouseMoveEvent() |
常用 | 鼠标拖动处理 | 使用默认行为 |
hoverEnterEvent() / hoverLeaveEvent() |
常用 | 悬停高亮等 | 无 hover 反馈 |
9.4 boundingRect()、shape()、实际绘制范围 三者区别
这是自定义里另一个高频混淆点。
| 名称 | 作用 | 精度 | 主要用途 |
|---|---|---|---|
boundingRect() |
图元外接矩形 | 粗 | 重绘优化、场景索引、更新区域 |
shape() |
图元真实形状路径 | 高 | 命中测试、碰撞检测、选择区域 |
paint() 画出来的内容 |
实际显示结果 | 取决于你 | 最终视觉表现 |
9.4.1 例子
你画一个圆:
boundingRect()可能返回包住圆的矩形shape()返回真正的圆形路径paint()里调用drawEllipse()
如果你只写 boundingRect() 不写 shape(),
那点击检测有时会显得"像矩形在响应",不够精确。
十、QPainterPath 的特殊作用
10.1 QPainterPath 在自定义图元里特别重要
虽然 QPainterPath 不是图元,但在自定义 QGraphicsItem 时非常有用。
| 用途 | 说明 |
|---|---|
| 作为实际绘制路径 | painter->drawPath(path) |
作为 shape() 返回值 |
精确命中检测 |
| 计算轮廓边界 | path.boundingRect() |
| 表示复杂曲线/箭头/贝塞尔图形 | 非规则图元的基础 |
10.1.1 示例代码
例如:
cpp
class MyPathLikeItem : public QGraphicsItem
{
public:
QRectF boundingRect() const override
{
return m_path.boundingRect().adjusted(-2, -2, 2, 2);
}
QPainterPath shape() const override
{
return m_path;
}
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *,
QWidget *) override
{
painter->setPen(QPen(Qt::darkGreen, 2));
painter->drawPath(m_path);
}
private:
QPainterPath m_path;
};
十一、常见图元类型与对应类
| 图元类型 | 对应类 | 直接父类 | 备注 |
|---|---|---|---|
| 单实线/虚实线/双实线 | QGraphicsLineItem | QGraphicsItem | 线条样式通过 QPen 属性自定义;双实线需自定义(如多线或路径)。 |
| 矩形 | QGraphicsRectItem | QAbstractGraphicsShapeItem | 支持描边和填充;QAbstractGraphicsShapeItem 提供笔刷支持。 |
| 椭圆 | QGraphicsEllipseItem | QAbstractGraphicsShapeItem | 类似矩形,支持完整圆或椭圆。 |
| 多边形 | QGraphicsPolygonItem | QAbstractGraphicsShapeItem | 支持任意多边形顶点定义。 |
| 自定义复杂线/形状 | QGraphicsPathItem | QAbstractGraphicsShapeItem | 用于路径绘制,可扩展实现虚实线变体或双线。 |
总结与快速查询
最终决策流程图
-
我想画一个简单图形(线/矩形/路径)?
- 是 → 直接用现成类(
QGraphicsLineItem/QGraphicsRectItem/QGraphicsPathItem) - 否 → 下一步
- 是 → 直接用现成类(
-
我只是想在现成图形上加点交互或数据?
- 是 → 继承对应的现成类
- 否 → 下一步
-
我的图形本质是"有 pen/brush 的形状"?
- 是 → 继承
QAbstractGraphicsShapeItem - 否 → 下一步
- 是 → 继承
-
我的图形完全自定义或很复杂?
- 是 → 继承
QGraphicsItem
- 是 → 继承