直接继承QWidget并重写paintEvent是最简绘图路径;QPainter必须在paintEvent内构造且仅传this,启用抗锯齿需提前调setRenderHint,绘图坐标系原点在左上角,动画和交互中只用update()而非repaint()。

直接继承 QWidget 并重写 paintEvent 就是最短路径,不需要 QGraphicsView 或 QML 那套抽象层。
paintEvent 里怎么构造 QPainter
必须在 paintEvent 函数体内、且仅在此处构造 QPainter 实例,传入 this(即当前 widget)作为绘制设备:
-
QPainter构造时绑定的是当前 widget 的屏幕缓冲区,不是 QImage 或 QPixmap —— 这是实时绘制的前提 - 不能在类成员变量里存一个全局
QPainter,它不是线程安全的,也不能跨事件生命周期复用 - 如果需要抗锯齿,立刻调用
painter.setRenderHint(QPainter::Antialiasing),这个必须在任何绘图调用前设置 - 别忘了清理状态:每次
paintEvent都是干净上下文,不用手动 reset 变换或画笔,但你自己改过的状态(比如save())得配对restore()
drawRect / drawEllipse 等函数坐标怎么算
所有绘图函数使用的是 widget 的本地坐标系,原点在左上角,x 向右递增,y 向下递增 —— 和你调用 resize() 或布局计算时用的坐标系一致:
-
drawRect(10, 20, 100, 50)表示从 (10, 20) 开始,宽 100、高 50 的矩形,不是中心点 - 想居中画一个圆?别硬算:先
painter.translate(width()/2, height()/2),再drawEllipse(-50, -50, 100, 100) - 鼠标事件里的
e->pos()返回的也是这个坐标系,可直接用于绘图,无需转换 - 如果 widget 启用了高 DPI 缩放(
Qt::AA_EnableHighDpiScaling),rect()和size()返回的是逻辑尺寸,QPainter内部自动适配物理像素,不用手动乘 scale factor
update() 和 repaint() 到底该用哪个
绝大多数情况只用 update(),除非你明确需要立即重绘且能接受卡顿:
-
update()是异步的:把重绘请求放入事件队列,等当前函数返回、控制权交还给事件循环后再触发paintEvent—— 安全、不阻塞、支持合并多次请求 -
repaint()是同步强制重绘:立刻执行paintEvent,可能打断动画帧、引发闪烁、甚至导致重入(比如在paintEvent里又调repaint()) - 动画场景下,定时器里必须用
update();鼠标拖拽绘制时,mouseMoveEvent里也只调update(),别用repaint() - 如果发现界面“不动了”,先检查是否误用了
repaint()导致死循环,或者忘记调update()
为什么画出来是黑的/白的/根本没显示
最常见三类原因:背景没填、画笔/画刷没设、坐标超出 widget 范围:
- 默认 widget 背景是透明的,如果你没主动 fill,底下窗口内容会透上来 —— 加一行
painter.fillRect(rect(), Qt::white)看是否变正常 -
drawLine只受QPen影响,drawRect同时受QPen(边框)和QBrush(填充)影响;漏设setBrush就只画边框,内部是透明的 - 用
qDebug()打印rect()和你传给drawXXX的坐标,确认没画到负数区域或远超 widget 尺寸的位置 - 如果 widget 没被加入布局、也没显式
show()或resize(),它尺寸是 (0, 0),所有绘图都无效 —— 先确保widget.size()不是 (0, 0)
真正难的不是“怎么画”,而是“什么时候画”和“画多快”——比如动画中频繁 update() 但没做脏矩形裁剪,或者在高刷新率屏上没控制帧率,这些才是后期卡顿的根源。先让图形动起来,再考虑优化。


















