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(应该是这个版本)解决了大部分问题,但对于像列表、表格等存在脏区域优化的控件,仍然存在渲染问题。