用QPainter在paintEvent()中绘图并配合鼠标事件记录数据是最可控起点;必须重写paintEvent(),因QPainter只能在此安全绑定QWidget设备;需显式设置pen/brush/font;坐标计算须用qMin/qMax防反向;保存宜选QImage导出、QPixmap缓存;橡皮擦应双缓冲实现。

直接上结论:用 QPainter 在 QWidget 的 paintEvent() 里绘图,配合鼠标事件记录图形数据,是最可控、最易调试的起点。别一上来就用 QGraphicsView 或 QML Canvas——它们抽象层高,出问题时连“画没画出来”还是“画错地方了”都难定位。
为什么必须重写 paintEvent() 而不是随便 new 一个 QPainter
QPainter 不是“想画就能画”的工具,它必须绑定到一个有效的 QPaintDevice(比如 QWidget),而且只能在特定时机安全使用。Qt 只允许在 paintEvent()、QPixmap 构造过程、或 QImage 上下文中调用 QPainter::begin()。其他地方调用会静默失败或崩溃。
- 常见错误现象:
QPainter::begin: Paint device returned engine == 0, type: 1—— 这说明你试图在非合法设备上启动绘制 - 正确做法:所有绘图逻辑只放在
paintEvent()内,且第一行必须是QPainter painter(this);不要在mousePressEvent里直接画 - 性能影响:每次窗口重绘(比如被遮挡后恢复)都会触发
paintEvent(),所以你要把图形数据(点、线、矩形列表)存在成员变量里,而不是靠事件临时画一笔就丢
QPainter 绘图前必须设置的三件事
不设 pen/brush/font 就调 drawLine(),结果往往是“看不见”或“颜色不对”。Qt 默认 pen 是黑色、1px 宽、实线;brush 是透明(即不填充)。很多新手卡在这一步,以为是坐标错了。
-
painter.setPen(QPen(Qt::blue, 3, Qt::SolidLine))—— 线条颜色、宽度、样式必须显式指定,橡皮擦本质就是设成Qt::NoPen+ 白色QBrush -
painter.setBrush(QBrush(Qt::red))—— 绘制drawRect()、drawEllipse()时,没设 brush 就只有边框,没有填充 -
painter.setFont(QFont("Arial", 12))—— 文字默认字体极小,且平台依赖,不设就可能显示为方块或完全空白
鼠标画线/矩形/椭圆时,坐标怎么算才不翻车
Qt 坐标系原点在左上角,Y 向下增长。但用户拖拽时直觉是“从起点拉到终点”,而 drawRect() 和 drawEllipse() 都要求传入 QRect,其构造参数是 (x, y, width, height),不是两个对角点。
- 自由涂鸦:在
mouseMoveEvent()中把每个QPoint存进QVector<qpoint></qpoint>,paintEvent()里用drawPolyline()一次性画出整条轨迹 - 拖拽矩形:记录
pressPos和movePos,然后算QRect r(qMin(p.x(), m.x()), qMin(p.y(), m.y()), qAbs(m.x()-p.x()), qAbs(m.y()-p.y()))—— 必须取 min/max,否则矩形会反向或消失 - 拖拽椭圆:和矩形一样用外接矩形,
drawEllipse(r)即可;别用中心点+半径,那要自己换算,容易漏符号 - 容易踩的坑:鼠标坐标是 widget 相对坐标,别误用
QCursor::pos()(那是屏幕坐标),也别忘了event->pos()已经是 widget 内部坐标,无需再减geometry().topLeft()
保存画布内容时,QPixmap 和 QImage 该怎么选
想导出 PNG/JPEG,用 QImage;想做离屏缓存(比如实现橡皮擦局部重绘),用 QPixmap。两者不能混用:前者是像素数组,CPU 可读写;后者是平台相关图像,GPU 友好但不可直接访问像素。
- 保存为文件:
QImage image(widget->size(), QImage::Format_RGB32); image.fill(Qt::white); QPainter p(&image); p.drawImage(0, 0, yourCanvasImage); image.save("output.png"); - 橡皮擦实现难点:不是“画白”,而是“擦除已有内容”。推荐双缓冲:用
QImage存原始画布,另建一个QPixmap当前台绘制层,橡皮擦时在QPixmap上用Qt::NoBrush+Qt::NoPen画透明区域,最后合成 - 兼容性注意:
QPixmap::grabWidget()在 Qt 5.15+ 已废弃,必须改用QPixmap::fromImage()或render()到QPixmap
最常被忽略的一点:所有图形数据(点、线、矩形)必须设计成可序列化的结构,否则“保存工程文件”这一步根本无从下手。别等到最后才补 QDataStream struct Shape 开始就得考虑怎么写进二进制流。


















