QT入门第八天:绘图QPainter详解 | 零基础学QT
前言
前七天我们学习了环境搭建、信号与槽、常用控件、布局管理器、对话框、菜单栏工具栏状态栏、事件处理。今天我们来学习QT的绘图机制------QPainter。
绘图是GUI编程的重要部分。你看到的很多炫酷效果、自定义控件、图表、动画,本质上都是"画"出来的。QT提供了强大的2D绘图系统,核心就是QPainter类。
今天我们学习:
- QPainter的基本用法
- 画各种基本图形(直线、矩形、圆、椭圆、多边形...)
- 画笔QPen和画刷QBrush
- 画文字
- 画图片
- 坐标变换(平移、旋转、缩放)
- 渐变和抗锯齿
一、QPainter 基础
1.1 什么是QPainter
QPainter是QT的绘图工具类,它可以在"绘图设备"上画东西。常见的绘图设备有:
- QWidget:控件(最常用)
- QPixmap:图片
- QImage:图片(更底层)
- QPicture:记录绘图命令
💡 理解:QPainter就像一支画笔,绘图设备就是画布。你拿着画笔在画布上画画。
1.2 paintEvent 事件
绘图一般在paintEvent事件里进行。当控件需要重绘时,QT会自动调用paintEvent。
什么时候会触发重绘?
- 窗口第一次显示时
- 窗口大小改变时
- 窗口被遮挡后重新显示时
- 调用
update()或repaint()时
1.3 基本用法
cpp
#include <QPainter>
#include <QPaintEvent>
void MyWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event); // 这个参数暂时用不到,先忽略
// 1. 创建QPainter对象,传入this(在当前控件上画)
QPainter painter(this);
// 2. 设置画笔(线条的颜色、粗细等)
painter.setPen(QPen(Qt::blue, 2));
// 3. 设置画刷(填充的颜色)
painter.setBrush(QBrush(Qt::yellow));
// 4. 画图
painter.drawRect(50, 50, 200, 100);
}
⚠️ 注意:QPainter对象要在paintEvent里创建,不要在外面创建。
二、基本图形绘制
2.1 画直线 drawLine
cpp
// 画一条从(50, 50)到(200, 200)的直线
painter.drawLine(50, 50, 200, 200);
// 也可以用QPoint
painter.drawLine(QPoint(50, 50), QPoint(200, 200));
2.2 画矩形 drawRect
cpp
// 画矩形:左上角坐标(50, 50),宽200,高100
painter.drawRect(50, 50, 200, 100);
// 也可以用QRect
painter.drawRect(QRect(50, 50, 200, 100));
2.3 画圆角矩形 drawRoundedRect
cpp
// 圆角矩形:x, y, w, h, 圆角x半径, 圆角y半径
painter.drawRoundedRect(50, 50, 200, 100, 20, 20);
2.4 画椭圆/圆 drawEllipse
cpp
// 画椭圆:外接矩形的左上角(50, 50),宽200,高100
painter.drawEllipse(50, 50, 200, 100);
// 画圆:宽和高相等就是圆
painter.drawEllipse(100, 100, 100, 100); // 圆心(150, 150),半径50
💡 小技巧:drawEllipse的参数是"外接矩形",不是圆心+半径。如果想按圆心画,可以这样算:
cppint cx = 150, cy = 150, r = 50; // 圆心(150,150),半径50 painter.drawEllipse(cx - r, cy - r, 2*r, 2*r);
2.5 画多边形 drawPolygon
cpp
// 定义多边形的点
QPolygon polygon;
polygon << QPoint(100, 100);
polygon << QPoint(200, 50);
polygon << QPoint(300, 100);
polygon << QPoint(250, 200);
polygon << QPoint(150, 200);
// 画多边形(自动闭合)
painter.drawPolygon(polygon);
2.6 画弧线 drawArc
cpp
// 画弧线:外接矩形, 起始角度, 跨越角度
// 角度单位是1/16度,90度就是 90*16 = 1440
// 0度在3点钟方向,逆时针为正
painter.drawArc(50, 50, 200, 200, 0, 90 * 16); // 从0度画90度
2.7 画扇形 drawPie
cpp
// 扇形:和弧线类似,但会连接圆心
painter.drawPie(50, 50, 200, 200, 30 * 16, 120 * 16);
2.8 画弦 drawChord
cpp
// 弦:弧线的两个端点连起来
painter.drawChord(50, 50, 200, 200, 0, 120 * 16);
2.9 画点 drawPoint
cpp
painter.drawPoint(100, 100);
// 画多个点
QPoint points[] = {QPoint(10, 10), QPoint(20, 20), QPoint(30, 30)};
painter.drawPoints(points, 3);
三、QPen 画笔
QPen控制"线条"的样式:颜色、粗细、线型、端点样式、连接样式。
3.1 基本设置
cpp
// 创建画笔:蓝色,线宽2像素
QPen pen(Qt::blue, 2);
// 设置给painter
painter.setPen(pen);
// 也可以直接设置
painter.setPen(QPen(Qt::red, 3));
3.2 颜色
cpp
pen.setColor(Qt::red); // 预定义颜色
pen.setColor(QColor(255, 0, 0)); // RGB
pen.setColor(QColor("#FF0000")); // 十六进制
pen.setColor(QColor(255, 0, 0, 128)); // RGBA,带透明度
QT预定义的颜色有很多:Qt::red, Qt::green, Qt::blue, Qt::black, Qt::white, Qt::yellow, Qt::cyan, Qt::magenta, Qt::gray, Qt::darkGray, Qt::lightGray 等等。
3.3 线宽
cpp
pen.setWidth(3); // 线宽3像素
pen.setWidthF(2.5); // 支持小数
3.4 线条样式
cpp
pen.setStyle(Qt::SolidLine); // 实线(默认)
pen.setStyle(Qt::DashLine); // 虚线
pen.setStyle(Qt::DotLine); // 点线
pen.setStyle(Qt::DashDotLine); // 点划线
pen.setStyle(Qt::DashDotDotLine); // 双点划线
pen.setStyle(Qt::NoPen); // 没有线(不画边框)
3.5 端点样式
cpp
pen.setCapStyle(Qt::FlatCap); // 平端点(默认)
pen.setCapStyle(Qt::SquareCap); // 方端点(延长一点)
pen.setCapStyle(Qt::RoundCap); // 圆端点
3.6 连接样式
cpp
pen.setJoinStyle(Qt::MiterJoin); // 斜接(默认,尖角)
pen.setJoinStyle(Qt::BevelJoin); // 斜切
pen.setJoinStyle(Qt::RoundJoin); // 圆角连接
四、QBrush 画刷
QBrush控制"填充"的样式:颜色、图案、渐变等。
4.1 纯色填充
cpp
// 创建画刷:红色
QBrush brush(Qt::red);
painter.setBrush(brush);
// 直接设置
painter.setBrush(QBrush(Qt::yellow));
4.2 填充样式
cpp
brush.setStyle(Qt::SolidPattern); // 纯色填充(默认)
brush.setStyle(Qt::NoBrush); // 不填充
brush.setStyle(Qt::HorPattern); // 水平线
brush.setStyle(Qt::VerPattern); // 垂直线
brush.setStyle(Qt::CrossPattern); // 十字线
brush.setStyle(Qt::BDiagPattern); // 左斜线
brush.setStyle(Qt::FDiagPattern); // 右斜线
brush.setStyle(Qt::DiagCrossPattern); // 交叉斜线
brush.setStyle(Qt::Dense1Pattern); // 密点1
brush.setStyle(Qt::Dense2Pattern); // 密点2
// ... 还有 Dense3 ~ Dense7
4.3 渐变填充(后面详细讲)
cpp
QLinearGradient gradient(0, 0, 200, 200);
gradient.setColorAt(0, Qt::red);
gradient.setColor(1, Qt::blue);
painter.setBrush(gradient);
4.4 纹理填充
cpp
brush.setTexture(QPixmap(":/images/texture.png"));
五、画文字 drawText
5.1 基本用法
cpp
// 在指定位置画文字
painter.drawText(50, 50, "Hello QT!");
// 在指定矩形内画文字
painter.drawText(QRect(50, 50, 200, 100), Qt::AlignCenter, "居中显示的文字");
5.2 设置字体
cpp
QFont font;
font.setFamily("微软雅黑"); // 字体
font.setPointSize(16); // 字号
font.setBold(true); // 加粗
font.setItalic(true); // 斜体
font.setUnderline(true); // 下划线
font.setStrikeOut(true); // 删除线
painter.setFont(font);
5.3 文字颜色
文字颜色是由画笔控制的:
cpp
painter.setPen(QPen(Qt::red)); // 文字红色
painter.drawText(50, 50, "红色文字");
5.4 对齐方式
cpp
// 水平对齐
Qt::AlignLeft // 左对齐
Qt::AlignRight // 右对齐
Qt::AlignHCenter // 水平居中
Qt::AlignJustify // 两端对齐
// 垂直对齐
Qt::AlignTop // 顶部
Qt::AlignBottom // 底部
Qt::AlignVCenter // 垂直居中
// 组合使用
Qt::AlignCenter // 水平垂直都居中(= AlignHCenter | AlignVCenter)
5.5 测量文字大小
有时候需要知道文字有多宽多高,用来布局:
cpp
QFontMetrics fm(font);
int width = fm.horizontalAdvance("Hello"); // 文字宽度
int height = fm.height(); // 文字高度
六、画图片
6.1 drawPixmap
cpp
#include <QPixmap>
// 加载图片
QPixmap pixmap(":/images/logo.png");
// 在(50, 50)位置绘制原图
painter.drawPixmap(50, 50, pixmap);
// 绘制到指定大小(自动缩放)
painter.drawPixmap(50, 50, 200, 100, pixmap);
// 只画图片的一部分(裁剪)
painter.drawPixmap(50, 50, pixmap, 0, 0, 100, 100);
6.2 drawImage
QImage和QPixmap类似,但QImage更底层,可以直接操作像素:
cpp
#include <QImage>
QImage image(":/images/photo.jpg");
painter.drawImage(50, 50, image);
💡 区别:
- QPixmap:适合显示,针对屏幕显示优化
- QImage:适合编辑,可以直接读写像素
- 一般显示图片用QPixmap就够了
七、坐标变换
QPainter支持坐标变换,可以平移、旋转、缩放、扭曲坐标系。
7.1 平移 translate
cpp
painter.translate(100, 100); // 把原点移到(100, 100)
painter.drawRect(0, 0, 50, 50); // 实际画在(100, 100)的位置
7.2 旋转 rotate
cpp
painter.translate(150, 150); // 先移到中心点
painter.rotate(45); // 旋转45度(顺时针)
painter.drawRect(-50, -50, 100, 100); // 画一个旋转了45度的正方形
⚠️ 注意:rotate是绕原点旋转的,所以一般先translate把原点移到旋转中心,再rotate。
7.3 缩放 scale
cpp
painter.scale(2, 1.5); // x方向放大2倍,y方向放大1.5倍
painter.drawRect(0, 0, 50, 50); // 实际大小是 100 x 75
7.4 扭曲 shear
cpp
painter.shear(0.5, 0); // x方向扭曲
painter.drawRect(0, 0, 100, 100);
7.5 save() 和 restore()
坐标变换是累积的,变换一次后后面的画图都会受影响。如果只想让某个图形应用变换,可以用save()保存状态,画完再restore()恢复:
cpp
painter.save(); // 保存当前状态
painter.translate(100, 100);
painter.rotate(30);
painter.drawRect(0, 0, 50, 50); // 这个矩形会平移+旋转
painter.restore(); // 恢复状态
painter.drawRect(0, 0, 50, 50); // 这个矩形不受影响
💡 好习惯:每次变换前save,画完restore,避免影响后面的绘图。
八、抗锯齿
默认情况下,画出来的线条会有锯齿(马赛克感)。开启抗锯齿后,边缘会更平滑。
cpp
// 开启抗锯齿
painter.setRenderHint(QPainter::Antialiasing);
// 文字抗锯齿
painter.setRenderHint(QPainter::TextAntialiasing);
// 平滑变换(图片缩放时更平滑)
painter.setRenderHint(QPainter::SmoothPixmapTransform);
💡 建议:一般都把抗锯齿打开,画出来的效果好看很多。性能影响不大,不用太担心。
九、渐变
QT支持三种渐变:线性渐变、径向渐变、锥形渐变。渐变可以作为画刷来填充图形。
9.1 线性渐变 QLinearGradient
从一条线到另一条线的渐变:
cpp
// 从(0, 0)到(200, 200)的线性渐变
QLinearGradient gradient(0, 0, 200, 200);
// 设置渐变颜色点(0是起点,1是终点)
gradient.setColorAt(0, Qt::red);
gradient.setColorAt(0.5, Qt::yellow);
gradient.setColorAt(1, Qt::blue);
// 作为画刷
painter.setBrush(gradient);
painter.setPen(Qt::NoPen);
painter.drawRect(0, 0, 200, 200);
9.2 径向渐变 QRadialGradient
从圆心向外辐射的渐变:
cpp
// 圆心(100, 100),半径100
QRadialGradient gradient(100, 100, 100);
gradient.setColorAt(0, Qt::white); // 中心白色
gradient.setColor(1, Qt::blue); // 边缘蓝色
painter.setBrush(gradient);
painter.setPen(Qt::NoPen);
painter.drawEllipse(0, 0, 200, 200);
9.3 锥形渐变 QConicalGradient
绕一个点旋转的渐变:
cpp
// 中心(100, 100),起始角度0度
QConicalGradient gradient(100, 100, 0);
gradient.setColorAt(0, Qt::red);
gradient.setColorAt(0.5, Qt::green);
gradient.setColorAt(1, Qt::blue);
painter.setBrush(gradient);
painter.setPen(Qt::NoPen);
painter.drawEllipse(0, 0, 200, 200);
9.4 渐变扩散方式
渐变还可以设置超出范围时的扩散方式:
cpp
gradient.setSpread(QGradient::PadSpread); // 填充(默认)
gradient.setSpread(QGradient::RepeatSpread); // 重复
gradient.setSpread(QGradient::ReflectSpread); // 镜像反射
十、综合实战:绘制漂亮的仪表盘
我们来画一个漂亮的仪表盘,综合运用今天学的知识。
10.1 完整代码
cpp
#include <QWidget>
#include <QPainter>
#include <QPaintEvent>
#include <QTimer>
class GaugeWidget : public QWidget
{
Q_OBJECT
public:
GaugeWidget(QWidget *parent = nullptr) : QWidget(parent) {
m_value = 0;
m_targetValue = 60;
// 定时器,让指针动起来
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, [=](){
if (m_value < m_targetValue) {
m_value += 1;
update(); // 触发重绘
} else {
timer->stop();
}
});
timer->start(20);
setWindowTitle("漂亮仪表盘");
resize(400, 400);
}
void setValue(int value) {
m_targetValue = value;
}
protected:
void paintEvent(QPaintEvent *event) override {
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
int w = width();
int h = height();
int side = qMin(w, h);
// 平移到中心,缩放坐标系
painter.translate(w / 2, h / 2);
painter.scale(side / 200.0, side / 200.0);
// ===== 1. 画外框(渐变圆环)=====
QRadialGradient outerGradient(0, 0, 100, 0, 0, 90);
outerGradient.setColorAt(0, QColor(60, 60, 80));
outerGradient.setColorAt(1, QColor(30, 30, 50));
painter.setBrush(outerGradient);
painter.setPen(Qt::NoPen);
painter.drawEllipse(-95, -95, 190, 190);
// ===== 2. 画内圈(深色)=====
QRadialGradient innerGradient(0, 0, 85, 0, 0, 80);
innerGradient.setColorAt(0, QColor(40, 40, 60));
innerGradient.setColorAt(1, QColor(20, 20, 40));
painter.setBrush(innerGradient);
painter.drawEllipse(-85, -85, 170, 170);
// ===== 3. 画刻度 =====
painter.save();
painter.rotate(-135); // 从-135度开始
QPen tickPen;
tickPen.setCapStyle(Qt::RoundCap);
for (int i = 0; i <= 100; i++) {
if (i % 10 == 0) {
// 长刻度
tickPen.setColor(QColor(200, 200, 220));
tickPen.setWidth(2);
painter.setPen(tickPen);
painter.drawLine(0, -75, 0, -60);
// 刻度数字
painter.save();
painter.translate(0, -50);
painter.rotate(135 - i * 2.7); // 转回来让文字正的
QFont font;
font.setPointSize(8);
font.setBold(true);
painter.setFont(font);
painter.setPen(QColor(200, 200, 220));
painter.drawText(QRect(-15, -8, 30, 16), Qt::AlignCenter, QString::number(i));
painter.restore();
} else if (i % 5 == 0) {
// 中刻度
tickPen.setColor(QColor(150, 150, 180));
tickPen.setWidth(1.5);
painter.setPen(tickPen);
painter.drawLine(0, -72, 0, -62);
} else {
// 短刻度
tickPen.setColor(QColor(100, 100, 140));
tickPen.setWidth(1);
painter.setPen(tickPen);
painter.drawLine(0, -70, 0, -65);
}
painter.rotate(2.7); // 总共270度,100格,每格2.7度
}
painter.restore();
// ===== 4. 画数值范围颜色条 =====
painter.save();
painter.rotate(-135);
for (int i = 0; i < 100; i++) {
QColor color;
if (i < 60) {
color = QColor(0, 200, 100); // 绿色
} else if (i < 80) {
color = QColor(255, 200, 0); // 黄色
} else {
color = QColor(255, 80, 80); // 红色
}
if (i <= m_value) {
painter.setPen(QPen(color, 3));
} else {
painter.setPen(QPen(QColor(50, 50, 70), 3));
}
painter.drawLine(0, -80, 0, -77);
painter.rotate(2.7);
}
painter.restore();
// ===== 5. 画指针 =====
painter.save();
painter.rotate(-135 + m_value * 2.7);
// 指针主体
QLinearGradient needleGradient(0, -70, 0, 10);
needleGradient.setColorAt(0, QColor(255, 100, 100));
needleGradient.setColorAt(1, QColor(200, 50, 50));
painter.setBrush(needleGradient);
painter.setPen(Qt::NoPen);
QPolygon needle;
needle << QPoint(-3, 10);
needle << QPoint(0, -70);
needle << QPoint(3, 10);
painter.drawPolygon(needle);
painter.restore();
// ===== 6. 画中心圆 =====
QRadialGradient centerGradient(0, 0, 15, 0, -5, 5);
centerGradient.setColorAt(0, QColor(80, 80, 100));
centerGradient.setColorAt(1, QColor(40, 40, 60));
painter.setBrush(centerGradient);
painter.setPen(QPen(QColor(20, 20, 40), 2));
painter.drawEllipse(-12, -12, 24, 24);
// ===== 7. 画数值显示 =====
painter.setPen(QColor(200, 200, 220));
QFont valueFont;
valueFont.setPointSize(16);
valueFont.setBold(true);
painter.setFont(valueFont);
painter.drawText(QRect(-50, 20, 100, 30), Qt::AlignCenter, QString::number(m_value));
QFont unitFont;
unitFont.setPointSize(7);
painter.setFont(unitFont);
painter.setPen(QColor(150, 150, 180));
painter.drawText(QRect(-50, 45, 100, 20), Qt::AlignCenter, "km/h");
}
private:
int m_value;
int m_targetValue;
};
10.2 运行效果
运行后你会看到一个非常漂亮的仪表盘:
- 深色渐变背景,很有科技感
- 绿色→黄色→红色的颜色条
- 指针从0开始动画到60
- 中心有数值显示
- 刻度和数字清晰
只用了一个paintEvent,就画出了这么炫酷的效果!
十一、今日总结
今天我们学习了QT的绘图系统QPainter,这是做自定义控件和炫酷效果的基础。
知识点汇总
| 类别 | 内容 | 常用方法/类 |
|---|---|---|
| 基本图形 | 直线、矩形、圆、椭圆、多边形、弧线 | drawLine, drawRect, drawEllipse, drawPolygon |
| 画笔 | 线条颜色、粗细、样式 | QPen, setColor, setWidth, setStyle |
| 画刷 | 填充颜色、图案、渐变 | QBrush, setColor, setStyle, QGradient |
| 文字 | 字体、大小、对齐 | drawText, QFont, QFontMetrics |
| 图片 | 绘制图片 | drawPixmap, drawImage, QPixmap |
| 坐标变换 | 平移、旋转、缩放 | translate, rotate, scale, save/restore |
| 效果 | 抗锯齿 | setRenderHint, Antialiasing |
| 渐变 | 线性、径向、锥形 | QLinearGradient, QRadialGradient, QConicalGradient |
重要概念
- ✅ paintEvent:绘图都在这里面进行
- ✅ QPainter:画笔,在绘图设备上画画
- ✅ QPen:控制线条样式
- ✅ QBrush:控制填充样式
- ✅ 坐标变换:translate/rotate/scale,记得save和restore
- ✅ 抗锯齿:打开后效果更平滑
- ✅ 渐变:三种渐变方式,做出炫酷效果
经验分享
- 先平移再旋转:旋转是绕原点转的,想绕某个点转,先把原点移过去
- 善用save/restore:变换前保存,画完恢复,避免影响后面的绘图
- 用相对坐标:尽量用相对坐标(比如以中心为原点),这样控件缩放时不会变形
- 开启抗锯齿:一般都打开,效果好很多
- 复杂图形分步骤画:先画背景,再画中间层,最后画前景,一层一层来
- update()触发重绘:数据变了调用update(),不要直接调用paintEvent
十二、明日预告
明天我们将学习QT的文件操作(QFile、QTextStream、QDataStream)。
文件操作是编程的基本功,我们会学习:
- QFile读写文本文件
- QTextStream文本流
- QDataStream数据流(读写二进制)
- QFileInfo文件信息
- QDir目录操作
- 文件和目录的增删改查
学会文件操作,你就能保存和读取数据了!
📝 学习建议:绘图是QT的重点也是难点,多动手画才能掌握。
练习建议:
- 把今天的仪表盘代码敲一遍运行看看
- 试试画一个进度条(圆形的)
- 试试画一个饼图
- 试试画一个笑脸表情
绘图掌握了,你就能做出各种自定义控件和炫酷效果了!明天见,继续加油!💪