QWidget窗口抗锯齿圆角的一个实现方案(支持子控件)2
本方案使用了QGraphicsEffect,由于QGraphicsEffect对一些控件会有渲染问题,比如列表、表格等,所以暂时仅作为研究,优先其他方案
在之前的文章中(支持子控件的抗锯齿圆角方案),对于独立弹窗的抗锯齿圆角,使用一层遮罩来实现对窗口内容的裁切。
在很早之前还考虑过另外一种方案,既然QGraphicsEffect能够对控件进行一些特效处理,那自定义QGraphicsEffect也应该可以做到对内容裁剪。但当时仅在QComboBox下拉列表上进行了测试,没有达到预期(实际可能是Qt内部的bug),甚至导致了我对QGraphicsEffect原理的误解。
直接说方案。
方案
- 重写一个QGraphicsEffect,照着其他Qt提供的类,重写QGraphicsEffect::draw接口。
简单来说就是通过混合模式对sourcePixmap进行圆角位置的像素清除,支持抗锯齿
void draw(QPainter *painter)
{// 一些Qt的逻辑QPoint offset;Qt::CoordinateSystem system = sourceIsPixmap() ? Qt::LogicalCoordinates : Qt::DeviceCoordinates;QPixmap pixmap = sourcePixmap(system, &offset, QGraphicsEffect::NoPad);if (pixmap.isNull())return;painter->save();QPainter pixmapPainter(&pixmap);pixmapPainter.setRenderHints(QPainter::Antialiasing); // 打开抗锯齿pixmapPainter.setPen(Qt::NoPen);pixmapPainter.setBrush(Qt::red); //颜色不重要,非透明即可pixmapPainter.setCompositionMode(QPainter::CompositionMode_DestinationOut); // 混合模式,达到清楚圆角部分像素目的QPainterPath path;// _target是目标QWidget,可以通过构造函数自己保存// 区域增大一点,避免边界有残留path.addRect(QRect(QPoint(0, 0), _target->size()).adjusted(-1, -1, 1, 1));path.addRoundedRect(QRect(QPoint(0, 0), _target->size()), 20, 20);// 一些Qt的绘制逻辑if (system == Qt::DeviceCoordinates) {QTransform worldTransform = painter->worldTransform();worldTransform *= QTransform::fromTranslate(-offset.x(), -offset.y());pixmapPainter.setWorldTransform(worldTransform);} else {pixmapPainter.translate(-offset);}pixmapPainter.drawPath(path);pixmapPainter.end();painter->setWorldTransform(QTransform());painter->drawPixmap(offset, pixmap);painter->restore();
};
上述代码里包含Qt的一些代码逻辑,没有具体研究过差异。
- 设置给目标控件即可
下拉框动画过程中会存在一些黑色像素,可以关闭动画。主要还是建议在相对静态的控件中使用。
结论
个人理解Qt的QGraphicsEffect里有相当多的问题,较早的版本可能对窗口的子控件无效,后期增加了对子控件的统一渲染支持,直到Qt6.4(应该是这个版本)解决了大部分问题,但对于像列表、表格等存在脏区域优化的控件,仍然存在渲染问题。