3. 基本图形绘制:直线、矩形、椭圆、圆弧、多边形、路径
好,咱们今天来聊聊 Qt 绘图里最基础、也最核心的东西——基本图形的绘制。
说实话,我刚开始接触 Qt 绘图时,觉得这玩意儿不就是画几个形状嘛,有啥难的?后来真正做项目才发现,越是基础的东西,坑越多。你想想看,一个界面好不好看,交互流不流畅,很大程度上就取决于这些基本图形画得对不对、快不快。
3.1 绘制直线
直线是最简单的图形。在 Qt 里,我们用 QPainter::drawLine() 来画。
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(QPen(Qt::blue, 2));
// 画一条从 (20, 20) 到 (200, 20) 的直线
painter.drawLine(20, 20, 200, 20);
// 也可以用 QPoint 来指定
QPoint p1(20, 50);
QPoint p2(200, 50);
painter.drawLine(p1, p2);
}
嗯,这里要注意:drawLine 不包含终点像素。什么意思呢?就是如果你画一条从 (0,0) 到 (10,0) 的线,它实际上只画了 10 个像素,而不是 11 个。我在做波形图控件时,就因为这个细节导致线条末端总是缺一截,排查了半天。
drawPolyline() 或者 QPainterPath 效率更高。
3.2 绘制矩形
矩形在 UI 里太常见了——按钮背景、边框、进度条,到处都是。
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(QPen(Qt::red, 2));
painter.setBrush(QBrush(Qt::yellow));
// 画一个普通矩形
painter.drawRect(20, 20, 150, 100);
// 画圆角矩形
painter.drawRoundedRect(200, 20, 150, 100, 10, 10);
}
我个人习惯用 QRect 来管理矩形区域,这样代码更清晰。比如:
QRect rect(20, 20, 150, 100);
painter.drawRect(rect);
setBrush,矩形内部会被填充。如果不想要填充,记得设置 setBrush(Qt::NoBrush)。我曾经在做一个图表控件时,忘了取消画刷,结果所有矩形都带了个白色背景,把底下的网格线全盖住了。
3.3 绘制椭圆和圆
椭圆其实是用矩形来定义的。你给一个矩形,Qt 就在这个矩形内画一个内切椭圆。
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(QPen(Qt::green, 2));
// 画椭圆
painter.drawEllipse(20, 20, 150, 100); // 宽150,高100
// 画正圆(宽高相等)
painter.drawEllipse(200, 20, 100, 100); // 直径100的圆
}
说白了,drawEllipse 的参数和 drawRect 是一样的。你给一个矩形区域,它就在里面画椭圆。所以想画正圆?让宽高相等就行。
我建议你记住这个规律:所有用矩形定义的图形,都可以用 QRect 来管理。这样代码复用性会好很多。
3.4 绘制圆弧
圆弧稍微复杂一点。它需要指定起始角度和跨度角度。
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(QPen(Qt::magenta, 2));
QRect rect(20, 20, 150, 150);
// 从0度开始,画90度的弧
int startAngle = 0 * 16; // 起始角度(单位:1/16度)
int spanAngle = 90 * 16; // 跨度角度(单位:1/16度)
painter.drawArc(rect, startAngle, spanAngle);
}
为什么角度要乘以 16?这是 Qt 的历史遗留设计。角度单位是 1/16 度,所以 90 度要写成 90 * 16。说实话,我第一次用的时候也觉得这个设计挺奇怪的,但用习惯了也就那样。
startAngle:起始角度,0 度在 3 点钟方向spanAngle:跨度角度,正值为逆时针,负值为顺时针- 角度单位:1/16 度,所以 360 度 = 360 * 16
3.5 绘制多边形
多边形用 QPolygon 来定义,然后传给 drawPolygon。
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(QPen(Qt::darkCyan, 2));
painter.setBrush(QBrush(Qt::lightGray));
QPolygon polygon;
polygon << QPoint(50, 10)
<< QPoint(10, 80)
<< QPoint(90, 80);
painter.drawPolygon(polygon);
}
这里有个细节:drawPolygon 会自动闭合图形,也就是把最后一个点和第一个点连起来。如果你不想闭合,用 drawPolyline。
我在做地图标注控件时,经常用多边形来画各种形状的标记。比如三角形箭头、五角星什么的。用 QPolygon 配合数学计算,可以画出任意形状。
3.6 绘制路径
路径是 Qt 绘图里最强大的工具。它可以把直线、曲线、圆弧组合成一个复杂的图形。
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(QPen(Qt::darkRed, 2));
painter.setBrush(QBrush(Qt::lightYellow));
QPainterPath path;
path.moveTo(20, 20); // 移动到起点
path.lineTo(100, 20); // 画直线到 (100, 20)
path.quadTo(150, 20, 150, 70); // 画二次贝塞尔曲线到 (150, 70)
path.cubicTo(150, 120, 100, 120, 100, 70); // 画三次贝塞尔曲线
path.closeSubpath(); // 闭合路径
painter.drawPath(path);
}
路径的好处是:你可以先定义好整个形状,然后一次性画出来。而且路径支持各种变换,比如平移、旋转、缩放。
QPainterPath 是最优雅的方式。它比一条条画线要清晰得多,也更容易维护。
3.7 知识体系总览
下面这张图总结了本章的核心内容,你可以把它当作一个快速参考。
3.8 避坑指南
最后,分享几个我踩过的坑:
- 画笔宽度为奇数时,线条会模糊——这是因为 Qt 默认的对齐方式导致的。解决办法:把坐标偏移 0.5 像素,或者用偶数宽度。
- 圆弧角度搞反了——记住:正角度是逆时针,负角度是顺时针。我刚开始总是记反。
- 路径不闭合时填充效果很奇怪——如果你用
drawPath并且设置了画刷,路径会自动闭合后再填充。如果不想要这个效果,用setBrush(Qt::NoBrush)。 - 性能问题——如果你在
paintEvent里频繁创建QPen、QBrush、QPainterPath对象,会导致性能下降。我建议把它们缓存成成员变量,只在需要改变时才重新创建。
好了,基本图形就讲到这里。这些内容虽然基础,但它们是所有复杂绘图的基础。你把这些练熟了,后面学渐变、变换、文本绘制就会轻松很多。