08-绘制基础:QPainter 画图与自定义控件¶
目录页:见 Qt 教程目录
目标¶
- 理解“自定义绘制”在做什么:你负责把像素画出来
- 会在 QWidget 里重写
paintEvent用 QPainter 画基本图形 - 做一个最小自定义控件:画一个进度条/仪表盘
先决知识(可选)¶
- 会搭一个简单窗口(见 05 章)
1. QPainter 是什么:你的“画笔”¶
- 专业名称:QPainter(2D 绘制引擎接口)
- 类比:画画:你拿笔、选颜色、画线、画矩形、写字
- 作用:在控件上绘制图形、文本、图片;实现自定义 UI
- 规则/坑点:绘制发生在
paintEvent里;不要在 paintEvent 里做耗时计算;需要抗锯齿时显式开启
2. paintEvent 是什么:系统让你“该重画了”¶
- 专业名称:paintEvent(绘制事件)
- 类比:系统对你说“现在这块区域需要重新画一遍”
- 作用:让你的控件在需要时正确显示(窗口遮挡、缩放、内容变化等都会触发)
- 规则/坑点:不要直接调用 paintEvent;更新时用
update()请求重绘
3. 最小自定义控件:画一个圆形进度(可运行)¶
这个例子做的事情:
- 一个自定义 QWidget,里面有一个 value(0~100)
- paintEvent 里用 QPainter 画背景圆 + 进度弧线 + 文字
#include <QApplication>
#include <QPainter>
#include <QTimer>
#include <QVBoxLayout>
#include <QWidget>
class Ring : public QWidget {
public:
explicit Ring(QWidget *parent = nullptr) : QWidget(parent) {}
void setValue(int v) {
if (v < 0) v = 0;
if (v > 100) v = 100;
if (value == v) return;
value = v;
update();
}
protected:
void paintEvent(QPaintEvent *) override {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing, true);
QRectF r = rect().adjusted(12, 12, -12, -12);
QPointF c = r.center();
qreal radius = qMin(r.width(), r.height()) / 2.0;
QPen bg(QColor("#e6e6e6"), 10);
bg.setCapStyle(Qt::RoundCap);
p.setPen(bg);
p.setBrush(Qt::NoBrush);
p.drawEllipse(c, radius, radius);
QPen fg(QColor("#4c8bf5"), 10);
fg.setCapStyle(Qt::RoundCap);
p.setPen(fg);
int span = static_cast<int>(-360.0 * 16.0 * (value / 100.0));
p.drawArc(r, 90 * 16, span);
p.setPen(QColor("#222222"));
QFont f = font();
f.setPointSize(16);
p.setFont(f);
p.drawText(rect(), Qt::AlignCenter, QString("%1%").arg(value));
}
private:
int value = 0;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget w;
w.setWindowTitle("QPainter Ring");
auto *layout = new QVBoxLayout(&w);
auto *ring = new Ring;
ring->setMinimumSize(220, 220);
layout->addWidget(ring);
int v = 0;
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, &w, [&] {
v = (v + 1) % 101;
ring->setValue(v);
});
timer.start(30);
w.show();
return app.exec();
}
你需要抓住两点:
- update() 不是立刻画,它是“请求系统稍后重绘”
- 真正的画发生在 paintEvent 的 QPainter 里
4. 坐标与单位:你画的是“控件坐标系”¶
直觉记法:
- (0,0) 在左上角
- x 向右增大,y 向下增大
- rect() 表示当前控件区域
规则/坑点: - DPI 不同会导致物理像素不同,但 Qt 会尽量帮你做缩放适配 - 你如果画得发虚,优先检查是否启用了抗锯齿,以及线宽/坐标是不是半像素导致模糊
常见坑¶
-
错误:在 paintEvent 里做网络请求/文件 I/O/复杂计算
结果:界面卡顿、拖动窗口都很慢
正确:把耗时工作放到别处,paintEvent 只负责画(数据准备好后update()) -
错误:希望“改变变量就自动刷新”,但没调用
update()
结果:值变了但画面不变
正确:数据变化后调用update()触发重绘
小练习¶
练习 1:做一个横向进度条¶
- 题目:自定义控件,画一个带圆角的横向进度条
- 输入/输出:value 变化时进度条长度变化
- 约束:必须在 paintEvent 里用 QPainter 画
- 提示:用
drawRoundedRect - 目标:练基本绘制 API
练习 2:加一层渐变¶
- 题目:把进度弧线改成渐变色
- 输入/输出:颜色从蓝到紫变化
- 约束:必须用 QLinearGradient/QRadialGradient
- 提示:画笔
QPen的 brush 可以用渐变 - 目标:练“画笔的刷子”
小结¶
- 自定义绘制=你在 paintEvent 里用 QPainter 把图画出来
- 数据变化后用
update()请求重绘 - 避免在 paintEvent 做耗时工作,保证界面丝滑
下一篇:见 09-线程与任务-不卡UI的正确姿势