Qt QWidget 独立窗口抗锯齿圆角的一个实现方案(支持子控件)

QWidget独立窗口抗锯齿圆角窗口的一个实现方案

由于 QWidget::setMask 接口设置圆角不支持抗锯齿,所以通常会使用透明窗口加圆角背景,但圆角背景不能满足对子控件的裁剪,子控件与圆角区域重叠的部分还是能显示出来。当然对于大多数窗口,留出足够的边距也是可以接受。

对一些特殊场景,比如QComboBox的列表框,UI设计师强烈要求圆角,列表与它的容器不能有边距,常规办法就很难做到。笔者在经过长时间的研究,有了一个可能的方案。

最终实现效果如下图,可以看到,列表项区域,滚动条区域也能够正常显示圆角。
抗锯齿圆角

注意,该方案可能不适用一些场景:

  1. 特殊的平台或Qt配置
  2. 复杂窗口且有性能要求
  3. 窗口尺寸较大(可以优化)
  4. 有嵌入式窗口、OpenGL、QWindow等

方案

基本原理

Qt的每个独立窗口,默认都是在一张图片上,层叠绘制所有子控件。通常我们自绘控件时,几乎不会使用QPainter::setCompositionMode设置其他混合模式,会出现比较奇怪的效果。但如果使用透明背景窗口,使用混合模式其实跟在QPixmap或QImage上绘制一样。

另外一点,当一个控件重绘时,由于底层的绘制会影响到上层透明合成,所以Qt会从下到上按顺序绘各个控件的脏区域。

所以理论上,如果在一个窗口上增加一个全尺寸的遮罩,重绘时使用混合模式就可以实现对一些像素的清除,且支持抗锯齿。

代码步骤

  1. 创建一个QWidget作为遮罩

    遮罩置于顶层,鼠标设置透传(WA_TransparentForMouseEvents)

    遮罩跟随窗口尺寸大小同步变化,保持一致。安装事件过滤器即可(installEventFilter)

  2. 重写paintEvent,利用混合模式清除圆角像素

    以下绘制逻辑比较直接,建议优化

	//创建一个图片,填充透明色
	QPixmap pix(this->size());
	pix.fill(QColor(0,0,0,0));
	// 在改图片上填充一个圆角区域,需要设置抗锯齿
	QPainter painter(&pix);
	painter.setRenderHint(QPainter::Antialiasing);
	QPainterPath path;
	//这里圆角区域需要根据dpi、size调整
	path.addRoundedRect(QRectF(pix.rect()).adjusted(0.5, 0.5, -0.5, -0.5), 10, 10); 
	painter.fillPath(path, Qt::white);
	painter.end();
	// 在窗口上绘制该圆角图片
	painter.begin(this);
	painter.setRenderHint(QPainter::Antialiasing);
	// 该混合模式会根据source像素的透明度,调整目标的透明度
	painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
	painter.drawPixmap(0, 0, pix);
	// 恢复默认混合模式,绘制边框,如果没有则不用
	painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
	painter.setPen(QPen(QColor(0xCA64EA), 1.0));
	painter.drawPath(path);

优化方向

  1. 性能优化

    上面的示例,使用了一整张图片对窗口像素进行混合模式的运算,且每次子控件重绘都会引起遮罩的重绘,性能比较差。可以考虑仅在四周设置圆角的遮罩。

  2. 圆角绘制优化

    本文使用了一个不透明的圆角区域对窗口设置裁剪,圆角的参数是固定在代码里的。实际QSS是可以设置窗口的圆角,因此可以借助QSS来生成一张圆角图片,就避免代码里包含固定数值。

    具体实现原理可以参考之前的文章,这里不具体展示了:
    QComboBox文字居中的一种解决办法
    Qt实现一个支持QSS的Switch Button
    Qt借助隐藏控件和QSS绘制重复元素

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt 中,QMainWindow 可以使用 QMdiArea 控件来管理多个窗口。如果需要安全释放 QMdiArea 控件窗口并清理窗口QWidget 内存,可以按照以下步骤进行操作: 1. 遍历 QMdiArea 控件窗口并删除它们 可以使用 `QMdiArea::subWindowList()` 方法来获取 QMdiArea 控件的所有窗口,然后调用 `QMdiSubWindow::close()` 方法来关闭每个窗口。例如: ```cpp foreach(QMdiSubWindow *subWindow, mdiArea->subWindowList()) { subWindow->close(); } ``` 2. 遍历 QMdiArea 控件窗口并删除它们的 QWidget 控件 在删除窗口之前,需要先删除窗口QWidget 控件。可以使用 `QWidget::findChildren()` 方法来获取每个窗口的所有 QWidget 控件,然后使用 `QLayout::removeWidget()` 方法将每个控件窗口的布局中移除,并调用 `delete` 运算符来释放内存。例如: ```cpp foreach(QMdiSubWindow *subWindow, mdiArea->subWindowList()) { QList<QWidget *> widgets = subWindow->widget()->findChildren<QWidget *>(); foreach(QWidget *widget, widgets) { QLayout *layout = widget->layout(); if (layout) { layout->removeWidget(widget); } delete widget; } } ``` 3. 删除 QMdiArea 控件窗口 最后,可以使用步骤 1 中的代码来删除 QMdiArea 控件窗口并释放内存。例如: ```cpp foreach(QMdiSubWindow *subWindow, mdiArea->subWindowList()) { subWindow->close(); } ``` 注意:在删除窗口控件时,一定要小心并仔细考虑,否则可能会导致程序崩溃。建议在删除窗口控件之前,先确保它们没有任何正在运行的操作。同时,在使用手动释放 QWidget 内存时,一定要注意内存泄漏问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值