在 Qt 的图形界面开发中,QPainter 是实现一切自定义视觉呈现的基石。无论是绘制简单的线条与形状,还是构建复杂的图表、动画乃至独特的 UI 控件,都依赖于 QPainter 强大的绘制能力。深入理解并掌握其 API,就如同掌握了在代码画布上挥洒创意的“神笔”,能够将任何视觉构思转化为现实。
本文将系统性地解析 QPainter 的核心功能,通过详尽的代码示例,带你从基础绘制逐步进阶到高级应用,最终实现一个完整的自定义控件。
一、QPainter 简介:Qt 的“画笔”
QPainter 提供了一套高度抽象的绘图 API,其核心能力涵盖:
- 基础几何图形:直线、矩形、椭圆、多边形等。
- 文本与字体:精细的文本绘制与排版控制。
- 图像与渐变:支持图片绘制和丰富的颜色填充效果。
- 坐标变换:平移、旋转、缩放、剪切等空间变换操作。
- 高级渲染:抗锯齿、多种合成模式、区域裁剪等。
它可以在多种绘图设备上工作:
- QWidget:通过重写
paintEvent() 方法进行绘制。
- QPixmap / QImage:用于离屏渲染或图像处理。
- QPrinter:将绘制内容输出到打印设备。
📌 核心使用原则:QPainter 必须在有效的绘制上下文内使用,通常是在 paintEvent() 函数中,或通过显式调用 begin() 和 end() 方法配对使用。
二、基础绘制:从“Hello World”开始
示例1:绘制一个带边框的矩形
让我们从一个最简单的例子开始,在自定义 Widget 上绘制一个矩形。
// mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
class MyWidget : public QWidget
{
Q_OBJECT
protected:
void paintEvent(QPaintEvent *event) override;
};
#endif // MYWIDGET_H
// mywidget.cpp
#include "mywidget.h"
#include <QPainter>
void MyWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(QPen(Qt::blue, 3)); // 设置蓝色画笔,宽度为3
painter.setBrush(QBrush(Qt::yellow)); // 设置黄色画刷用于填充
painter.drawRect(50, 50, 200, 100); // 绘制矩形 (x, y, 宽度, 高度)
}
✅ 效果:在窗口坐标 (50, 50) 的位置,出现一个宽200像素、高100像素的矩形,边框为蓝色,内部填充为黄色。这是理解 C++/Qt 图形绘制逻辑的起点。
三、深入 QPainter 核心 API 分类详解
我们将 qpainter.h 中的关键函数按功能分类,并辅以代码演示。
3.1 设置画笔(Pen)与画刷(Brush)
| 函数 |
作用 |
setPen() |
设置线条的颜色、宽度、样式(如实线、虚线、点线) |
setBrush() |
设置封闭图形的填充颜色或渐变样式 |
// 演示多种画笔样式
QVector<Qt::PenStyle> styles = {
Qt::SolidLine, Qt::DashLine, Qt::DotLine,
Qt::DashDotLine, Qt::DashDotDotLine
};
for (int i = 0; i < styles.size(); ++i) {
painter.setPen(QPen(Qt::black, 2, styles[i]));
painter.drawLine(10, 30 + i * 20, 200, 30 + i * 20);
}
// 使用线性渐变画刷进行填充
QLinearGradient gradient(0, 0, 200, 100);
gradient.setColorAt(0, Qt::red);
gradient.setColorAt(1, Qt::blue);
painter.setBrush(gradient);
painter.drawRect(0, 0, 200, 100);
3.2 绘制几何图形
| 函数 |
说明 |
drawLine() |
绘制直线 |
drawRect() / drawRoundedRect() |
绘制矩形 / 圆角矩形 |
drawEllipse() |
绘制椭圆或圆形 |
drawPolygon() / drawConvexPolygon() |
绘制多边形 |
drawPath() |
绘制由 QPainterPath 定义的任意路径,如贝塞尔曲线 |
示例:绘制一个五角星
QPointF points[5];
double radius = 80;
double cx = 150, cy = 150;
// 计算五个顶点的坐标
for (int i = 0; i < 5; ++i) {
double angle = M_PI / 2 - i * 2 * M_PI / 5;
points[i] = QPointF(cx + radius * cos(angle), cy + radius * sin(angle));
}
// 连接隔一个的顶点,形成五角星路径
QPolygonF star;
star << points[0] << points[2] << points[4] << points[1] << points[3] << points[0];
painter.setPen(Qt::NoPen); // 无边框
painter.setBrush(Qt::yellow);
painter.drawPolygon(star);
3.3 文本绘制
| 函数 |
功能 |
drawText() |
基础文本绘制 |
boundingRect() |
获取文本的理论包围矩形,用于精确对齐 |
setFont() |
设置字体族、大小、粗细等属性 |
QStaticText |
高性能静态文本对象,适合内容不变且需要频繁绘制的场景 |
示例:绘制带阴影效果的文本
painter.setFont(QFont("Arial", 24, QFont::Bold));
// 先绘制灰色阴影(偏移2像素)
painter.setPen(Qt::gray);
painter.drawText(102, 102, "Hello QPainter");
// 再绘制白色主体文本
painter.setPen(Qt::white);
painter.drawText(100, 100, "Hello QPainter");
3.4 图像与像素操作
| 函数 |
说明 |
drawPixmap() |
绘制 QPixmap,适用于界面显示,支持缩放和裁剪。 |
drawImage() |
绘制 QImage,可直接访问和操作像素数据。 |
QPixmap icon(":/icons/delete.png");
// 将图标绘制在(10,10)位置,并缩放到32x32像素
painter.drawPixmap(10, 10, 32, 32, icon);
3.5 坐标变换:让绘制“动”起来
坐标变换是实现复杂图形和动画效果的关键。
| 函数 |
作用 |
translate(dx, dy) |
平移坐标系原点 |
rotate(angle) |
旋转坐标系(角度制) |
scale(sx, sy) |
缩放坐标系 |
shear(sh, sv) |
剪切变换 |
save() / restore() |
保存/恢复当前绘图状态(采用栈式管理) |
示例:旋转文本
painter.save(); // 保存当前状态(如坐标系)
painter.translate(200, 200); // 将原点移动到(200,200)
painter.rotate(-45); // 坐标系逆时针旋转45度
painter.drawText(0, 0, "Rotated Text"); // 在新坐标系的原点绘制文本
painter.restore(); // 恢复之前的绘图状态,不影响后续绘制
✅ 最佳实践:进行任何变换操作前后,务必成对使用 save() 和 restore(),这是保证绘制逻辑清晰、避免状态污染的关键。
3.6 高级特性:抗锯齿、合成与裁剪
// 1. 启用抗锯齿,使曲线和斜边更平滑
painter.setRenderHint(QPainter::Antialiasing, true);
// 2. 设置裁剪区域,后续绘制只在该区域内生效
painter.setClipRect(50, 50, 100, 100);
painter.fillRect(rect(), Qt::red); // 只有(50,50,100,100)区域内会变红
// 3. 使用 Porter-Duff 合成模式,实现特殊混合效果
painter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
四、实战:打造一个自定义仪表盘控件
综合运用上述知识,我们创建一个美观的圆形进度仪表盘控件。
// gauge.h
#ifndef GAUGE_H
#define GAUGE_H
#include <QWidget>
class Gauge : public QWidget
{
Q_OBJECT
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
public:
explicit Gauge(QWidget *parent = nullptr);
int value() const { return m_value; }
void setValue(int v) { m_value = qBound(0, v, 100); update(); }
signals:
void valueChanged(int);
protected:
void paintEvent(QPaintEvent *) override;
private:
int m_value = 0;
};
#endif // GAUGE_H
// gauge.cpp
#include "gauge.h"
#include <QPainter>
#include <QConicalGradient>
void Gauge::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// 将坐标系中心移至控件中心,并缩放到合适大小
int side = qMin(width(), height());
painter.translate(width() / 2, height() / 2);
painter.scale(side / 200.0, side / 200.0); // 归一化到 200x200 的基准尺寸
// 1. 绘制背景圆环(使用径向渐变)
QRadialGradient bgGradient(0, 0, 90, 0, 0, 70);
bgGradient.setColorAt(0, QColor(60, 60, 60));
bgGradient.setColorAt(1, QColor(30, 30, 30));
painter.setBrush(bgGradient);
painter.setPen(Qt::NoPen);
painter.drawEllipse(-90, -90, 180, 180);
// 2. 绘制进度弧(使用锥形渐变,颜色从绿到红)
QConicalGradient arcGradient(0, 0, -90); // 起始角度为-90度(顶部)
arcGradient.setColorAt(0, Qt::green);
arcGradient.setColorAt(0.5, Qt::yellow);
arcGradient.setColorAt(1, Qt::red);
painter.setBrush(arcGradient);
painter.setPen(QPen(Qt::white, 8)); // 白色宽边
QRectF rect(-85, -85, 170, 170);
double angle = -270 * m_value / 100.0; // 根据进度值计算弧度(从顶部顺时针)
painter.drawArc(rect, -90 * 16, angle * 16); // Qt中角度单位为1/16度
// 3. 在中心绘制当前进度值文本
painter.resetTransform(); // 恢复到原始坐标系,便于文本定位
painter.setFont(QFont("Arial", 16));
painter.setPen(Qt::white);
painter.drawText(this->rect(), Qt::AlignCenter, QString::number(m_value) + "%");
}
✅ 效果:一个具有颜色渐变效果的圆形进度条,支持通过属性动画平滑更新进度值。这个实战案例充分展示了如何利用 QPainter 的渐变、路径绘制和坐标变换能力来构建复杂的图形界面组件。
五、性能优化技巧
在复杂的图形应用中,绘制性能至关重要。以下是一些实用的优化建议:
- 对象复用:避免在
paintEvent() 中频繁创建 QPen、QBrush、QFont 等对象,应在类初始化时创建并缓存。
- 静态文本:对于内容不变但需要频繁绘制的文本,使用
QStaticText 可以显著提升性能。
- 绘制指令缓存:对于复杂的静态图形,可以使用
QPicture 记录绘制指令,然后重复绘制该 QPicture。
- 局部更新:当只有部分区域需要重绘时,使用带参数的
update(const QRect &rect) 代替无参数的 update(),可以最小化重绘区域,提升效率。这在涉及算法优化的高频刷新场景中尤其重要。
六、结语:成为界面创造的“神笔马良”
通过系统性地学习 QPainter,你会发现自定义图形界面的本质是基本图形元素的组合、坐标空间的变换以及对细节的精确控制。它赋予你突破标准控件限制的能力,让你能够创造独一无二的用户体验。
“心中有坐标,万物皆可绘。” 现在,打开你的开发环境,从重写一个 QWidget 的 paintEvent() 开始,拿起 QPainter 这支“神笔”,去构建你想象中的视觉界面吧。