Qt底层原理:深入解析QWidget的绘制技术细节(2)

(本文续上一篇《Qt底层原理:深入解析QWidget的绘制技术细节(1)》)

QWidget绘制体系为什么这么设计【重点】

在传统的C++图形界面框架中,例如DUILib等,控件的绘制逻辑往往直接在控件的类的内部,例如PushButton的draw/paint的函数内部,Qt的QWidget费了老大劲,定义了一堆枚举和基类,把大部分的绘制逻辑都抽离了具体的类,转到了QStyle上。这种做法说实话,是有弊有利的。
下面是对利弊的详细讨论:

有利之处:

  1. 提高绘制逻辑的复用性

    • 在Qt中,绘制逻辑不是硬编码在每个控件中的,而是通过QStyle这个中心化的类来处理的。这意味着,像在QListView中绘制按钮这样的任务,不需要创建QPushButton实例,而是通过QStyle来绘制具有按钮视觉效果的元素。这样,任何需要具有按钮风格的控件都可以复用这一段绘制代码,大大提高了代码的复用性。
  2. 提高绘制逻辑的风格化能力

    • 由于QStyle负责所有控件的绘制细节,这使得统一应用程序的风格变得容易。开发者可以通过改变QStyle或者使用QStyleSheets来快速地修改应用程序的风格,而无需修改每个控件。这种能力使得开发者能够更快地响应设计的改变,并且为用户提供一致的视觉体验。
  3. 实现绘制逻辑和具体的控件类的解耦

    • 在传统的GUI框架中,绘制代码通常与控件逻辑紧密耦合。Qt通过将绘制逻辑抽象到QStyle中,实现了绘制逻辑与控件类的分离。这样的解耦使得开发者可以在不改变控件逻辑的情况下,通过修改QStyle或QStyleSheets来定制控件的外观。这也为实现像QSS这样的高级样式特性奠定了基础。

弊处:

  1. 增加了绘制逻辑的复杂度

    • Qt的这种抽象方式确实增加了学习和实现自定义绘制逻辑的复杂度。新加入的Qt开发者需要理解QStyle的工作原理以及如何与QStyleOption等类配合使用,这对于初学者而言可能是一个挑战。
  2. 给绘制体系新增控件增加了难度

    • 在Qt中,控件的绘制细节往往被封装成枚举类型,这些枚举在整个QStyle体系中都有明确的定义。当需要增加新的控件或者扩展控件的功能时,可能需要在QStyle中添加新的枚举值,并要求所有的QStyle实现都支持这个新的枚举。这不仅增加了开发的难度,也可能导致现有的风格类需要进行大量的更新来适应新的枚举。

总结来说,Qt选择这种设计,核心是2个考虑,第一个是性能,就如前面提到的,当我们需要绘制一个按钮的时候是不需要实例化按钮类的,这给QListView的性能天花板打到比其他任何图形界面框架都要高。另一个方面是实现非常接近原生的界面风格元素,这也是Qt界面框架和其他界面框架独特之处。Qt界面默认情况下是可以达到以假乱真的原生效果,要实现如此高度的还原,还要保障绘制的性能,那么把所有绘制逻辑针对不同平台提供高度的定制化是必然的做法,因此QStyle这套体系就形成了。

绘制双缓冲细节

在Qt中,为了在绘制时不在屏幕出现绘制过程导致画面闪烁,会采用双缓冲机制。与此相关的一些类和组件包括:

  1. QPixmap:
    QPixmap是一个用于处理图像的类,通常用于离屏绘制(off-screen drawing)。它可以作为双缓冲的后台缓冲区使用,在这个缓冲区上进行绘制操作,然后将其内容一次性绘制到屏幕上。

  2. QWidget:
    QWidget类有一个属性,决定是否使用双缓冲。默认情决定了Qt是否为QWidget启用双缓冲。大多数情况下,Qt会自动为所有的QWidget及其子类使用双缓冲策略,但是开发者可以通过setAttribute(Qt::WA_PaintOnScreen)来修改这个行为。

  3. QBackingStore:
    QBackingStore是Qt中负责管理窗口内容的后台存储的类。它是Qt双缓冲机制的核心组件之一,在窗口系统层面处理缓冲区。当窗口或部件的内容需要更新时,QBackingStore负责将缓冲区的内容复制到屏幕上。

  4. QPaintEngine:
    QPaintEngine是一个抽象基类,它定义了Qt绘图操作的底层接口。具体的实现类,如QRasterPaintEngine,会使用双缓冲技术来提高绘制效果和性能。

  5. QWindow:
    在Qt中,QWindow代表了一个系统窗口。它可以使用QBackingStore来管理其内容的双缓冲,尤其是在Qt Quick中,QWindow是与平台窗口系统交互的主要接口。

  6. QScreen:
    QScreen类代表了应用程序可以使用的显示器。虽然它不直接参与双缓冲,但是它提供了与屏幕相关的功能,包括分辨率、颜色深度等信息,这些信息可能会影响双缓冲策略的选择和优化。

在Qt的绘制过程中,当你在QWidget的paintEvent()方法中使用QPainter进行绘图时,你实际上是在绘制到一个离屏缓冲区。然后,该缓冲区的内容会被复制到屏幕上。这个过程对于开发者来说是透明的,因为Qt框架在底层处理了所有的细节。

如果需要控制双缓冲的行为,或者需要更深入地理解其实现,可以查看以上提到的类的文档和源代码。

需要注意的是,Qt Quick(基于QML的高级UI框架)与传统的QWidget系统在渲染上有所不同。Qt Quick使用场景图(scene graph)和通常基于OpenGL的渲染器进行绘制,而不是使用传统的QWidget绘制流程。尽管如此,QWindowQBackingStore仍然在Qt Quick的窗口管理和屏幕渲染中发挥作用。

如何提高应用程序的绘制性能

提高绘制性能通常涉及减少不必要的绘制工作和优化绘制路径。以下是一些策略来提高Qt控件的绘制性能:

  1. 避免半透明和透明度

    • 避免半透明的控件,因为它需要额外的合成步骤。
    • 使用不透明的控件,设置属性Qt::WA_OpaquePaintEvent
  2. 减少重绘区域

    • 只重绘变化的部分,而不是整个控件。
    • 使用QWidget::update(const QRect&)来指定只重绘控件的一个子区域。
    • 避免不必要的update()调用。
  3. 优化绘制代码

    • paintEvent中避免复杂计算。
    • 使用简单的几何图形和操作,避免绘制复杂的图形。
    • 避免在paintEvent中创建临时对象。
  4. 延迟更新

    • 使用QWidget::update()而不是QWidget::repaint(),因为update()会合并多个重绘请求,延迟到下一个事件循环中。
  5. 使用双缓冲

    • Qt默认使用双缓冲来避免闪烁,确保此功能未被禁用。
  6. 缓存绘制结果

    • 对于不经常变化的内容,可以将其缓存到QPixmapQImage中,然后在paintEvent中直接绘制这些缓存。
  7. 减少布局调整

    • 避免频繁的布局改变,特别是包含大量控件的布局。
  8. 使用QStaticTextQPixmap

    • 对于不更改的文本,使用QStaticText可以提高绘制性能。
    • 对于重复使用的图像,使用QPixmap进行缓存。
  9. 避免使用图形效果

    • 图形效果如阴影、模糊等会增加绘制负担,应谨慎使用。
  10. 合理使用更新策略

    • 对于自定义控件,使用QWidget::setUpdateRect()来定义更高效的更新策略。
  11. 使用硬件加速

    • 如果可能,利用OpenGL或Vulkan等进行硬件加速绘制。
  12. 多线程

    • 对于复杂的图像处理或准备工作,可以在后台线程中进行,以免阻塞UI线程。
  13. 调整渲染选项

    • 使用QPainter的渲染提示来平衡质量和性能。
  14. 避免无效的层级结构

    • 减少嵌套层次和不必要的父子控件关系。

需要注意的是,性能调整往往需要根据具体的应用场景和需求来定制,因此推荐在做出调整后进行充分的测试,以确保既达到了性能目标,又保持了用户界面的质量和响应性。

使用多线程绘制提高性能的例子

在Qt中,UI更新(包括绘制)必须在主线程(也就是UI线程)中完成。但是,我们可以在另一个线程中生成图像数据,然后将这些数据发送回主线程进行显示。下面是这种方法的主要流程:

  1. 在工作线程中生成图像
    创建一个工作线程,在这个线程中进行图像的生成或处理,比如绘制到一个QImage或者QPixmap对象上。这可以通过直接在工作线程中创建图像对象并使用QPainter来绘制。

  2. 使用信号和槽传输图像
    当图像生成完毕,使用信号和槽机制将图像从工作线程发送回主线程。这通常涉及到在工作线程中发射一个信号,携带生成的图像作为参数。在主线程中,一个槽函数将会接收这个图像。

  3. 在主线程中显示图像
    在主线程的槽函数中接收图像,并将其设置到一个控件上显示。这可以是通过调用QLabel::setPixmap()设置QPixmap,或者在自定义控件的paintEvent()中使用QPainter::drawImage()来绘制QImage

以下是一个简化的代码示例,展示了如何在工作线程中生成图像,并在主线程中显示:

// MyWorkerThread.h
#include <QThread>
#include <QImage>class MyWorkerThread : public QThread {Q_OBJECTpublic:MyWorkerThread(QObject *parent = nullptr) : QThread(parent) {}signals:void imageReady(const QImage &image);protected:void run() override {QImage image(100, 100, QImage::Format_ARGB32);QPainter painter(&image);// ... 在这里进行绘制操作 ...emit imageReady(image);}
};// MyWidget.h
#include <QWidget>
#include <QImage>class MyWidget : public QWidget {Q_OBJECTpublic:MyWidget(QWidget *parent = nullptr) : QWidget(parent) {// Start the worker threadconnect(&workerThread, &MyWorkerThread::imageReady, this, &MyWidget::updateImage);workerThread.start();}~MyWidget() {workerThread.quit();workerThread.wait();}public slots:void updateImage(const QImage &image) {this->image = image;update(); // Schedule a repaint}protected:void paintEvent(QPaintEvent *event) override {QPainter painter(this);if (!image.isNull()) {painter.drawImage(0, 0, image);}}private:MyWorkerThread workerThread;QImage image;
};

在上面的例子中,MyWorkerThread类在一个工作线程中生成了一个QImage。一旦图像生成完毕,它通过信号imageReady将图像发送回主线程。MyWidget类有一个槽函数updateImage来接收图像,并使用update()方法请求重绘。在paintEvent()中,接收到的图像被绘制在控件上。
当在工作线程中使用QImage时,应该使用线程安全的图像格式,如QImage::Format_ARGB32QPixmap是专门为显示优化的,并且通常不应在非UI线程中使用。

通过这两篇文章,相信大家对Qt的绘制体系有了总体上的印象,并且对Qt绘制体系的设计缘由也更加清晰。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/30502.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Android】怎么使APP进行开机启动

项目需求 在Android系统开启之后&#xff0c;目标app可以在系统开机之后启动。 项目实现 使用广播的方式 首先我们要创建一个广播(这里是启动了一个Service服务) public class BootReceiver extends BroadcastReceiver {Overridepublic void onReceive(Context context, I…

EXCELITAS电源维修TLX302高压电源维修

埃赛力达电源维修 EXCELITAS电源维修 海曼电源维修 高压电源维修 EXCELITAS高压电源维修故障包括&#xff1a;无输出&#xff0c;高压达不到&#xff0c;电流达不到标准&#xff0c;高压打火,高压线接头处太靠近铁壳部分。无光,风扇不转。保险丝断&#xff0c;可以强制发光,不…

Java——构造器(构造方法)和 this

一、什么是构造器 构造器&#xff08;Constructor&#xff09;是Java类的一种特殊方法&#xff0c;用于初始化对象的状态。构造器在创建对象时被调用&#xff0c;可以对对象的成员变量进行初始化。 我之前的文章《Java——类和对象-CSDN博客》中也提到了构造器。 二、构造器…

文件二维码怎么快速生成?在线文件生码的使用技巧

文件现在经常会做成二维码的方式来展示内容&#xff0c;通过这种方式能够更加简单快捷的将文件分享给其他人查看或者下载&#xff0c;而且文件生成活码可以长期使用&#xff0c;随时替换当前二维码中的内容&#xff0c;那么可以长期使用的文件二维码该如何制作呢&#xff1f; …

Android开发Activity生命周期详解

本文详解Android开发Activity生命周期。 目录 一、Activity 二、Activity生命周期 三、生命周期特性 四、常见情况生命周期的执行顺序 一、Activity Activity是用户交互的第一接口&#xff0c;它提供了一个用户完成指令的窗口。当开发者创建Activity之后&#xff0c;通过…

bms电池管理系统中放电过流1,放电过流2,放电过流3,分别是什么意思

在电池管理系统&#xff08;BMS&#xff09;中&#xff0c;“放电过流1”、“放电过流2”、“放电过流3” 这些术语可能代表了不同级别的放电过电流保护阈值或状态。这些级别通常是基于电池的安全工作范围和性能特性来设定的&#xff0c;用以在不同的电流水平下提供不同程度的保…

Springboot 集成 Shardingsphere-JDBC

Springboot 集成 Shardingsphere-JDBC Shardingsphere系列目录&#xff1a;背景前提新增依赖分表策略简单分库分表策略垂直分库广播表水平分库(单表)水平分库(多表)水平分表 HINT配置逻辑代码 自定义分库分表&#xff08;精准定位范围查询&#xff09;配置代码精准定位数据库精…

什么是本地启动?

今天在进行接口测试的时候&#xff0c;我刚开始傻乎乎的&#xff0c;不会测试嘛&#xff0c;那个 postman 里面叫你填那个URL&#xff0c;我就把设备管理系统的地址填了上去&#xff1a;http://192.168.0.237.27000/。 睿哥看了之后&#xff0c;跟我说&#xff1a;“你填这个地…

Linux DNS配置文档

一、问题描述 1. 无法在浏览器通过域名访问百度&#xff1b; 2. 无法在终端 ping 通百度&#xff0c;例如&#xff1a;ping www.baidu.com 3. 可以 ping 通公网地址&#xff0c;例如&#xff1a;ping 114.114.114.114 或 ping 8.8.8.8 二、问题原因 域名解析 DNS 配置错误&am…

数据结构历年考研真题对应知识点(单链表、双链表、循环链表)

目录 2.3线性表的链式表示 2.3.1单链表的定义 【单链表的应用(2009、2012、2013、2015、2016、2019)】 2.3.2单链表上基本操作的实现 【单链表插入操作后地址或指针的变化(2016)】 2.3.3双链表 【双链表中插入操作的实现(2023)】 【循环双链表中删除操作的实现(2016)】 …

如何抓取 GitHub:实用教程 2024

GitHub 是互联网上最重要的技术知识来源之一&#xff0c;对于构建复杂应用程序的开发人员来说尤其如此。跟随本指南学习如何提取这些宝贵的数据&#xff0c;毫不费力地紧跟最新技术趋势。 了解 GitHub 数据 开源项目文化为开发人员提供了许多分享、贡献和合作的机会&#xff…

多个类下所有方法AOP

微服务下有两个类&#xff0c;需要做异常捕获再抛出&#xff0c;笔者立马想到了AOP 非微服务&#xff0c;只能使用代理 Slf4j Aspect Component public class SdkAspect {Pointcut("execution(* com.aspire.service.impl.XxxEncryption.*(..))")public void enPointc…

android的surface

相信很多Android开发者都知道Canvas类是UI的画布&#xff08;虽然这种说法并不严谨&#xff09;&#xff0c;因为我们在Canvas上完成各种图形的绘制&#xff0c;那么我们Activity上的各种交互控件又是如何展示并渲染到屏幕上的呢&#xff0c;所以在另一个层面上也有一个“画布”…

安卓开发拉起其他应用的常用方式

在安卓开发中&#xff0c;拉起其他应用&#xff08;即启动其他应用&#xff09;有几种常见的方式&#xff1a; 通过显式 Intent&#xff1a; 这种方式需要知道目标应用的包名和具体的 Activity 名称。 Intent intent new Intent(); intent.setComponent(new ComponentName(…

地图上绘制地铁线路

需求背景 不管是之前的pms 地铁还是location都会有需求涉及到地图上绘制地铁线路&#xff0c;来查看当前位置是否靠近地铁口&#xff0c;常规的交互可以看下高德地图&#xff0c;如图所示&#xff1a; 需求分析 不管是高德地图还是百度地图都提供了简易版的地铁线路图&#x…

Excel如何设置自动更新的固定选项

日常工作中你是否想要某数据列设置固定选项&#xff0c;如人力组、财务组、综合组、业务组等&#xff0c;可用“数据验证”实现&#xff0c;如后期新增选项“党建组”&#xff0c;该如何快速处理&#xff1f; 今天刘小生分享“超级表数据验证”方式&#xff0c;只实现固定选项…

pytorch笔记:清理GPU内存

1 Control-C 中止运行GPU 存储没有及时释放 在使用 GPU 进行深度学习训练时&#xff0c;通过 Control-C 中止程序后&#xff0c;有时会发现 GPU 内存没有及时释放这主要是因为以下几个原因 进程未完全终止&#xff1a; 当我们按下 Control-C 时&#xff0c;只是发送了一个中断…

若依RuoYi-Vue分离版—富文本Quill的图片支持伸缩大小及布局

若依RuoYi-Vue分离版—富文本Quill的图片支持伸缩大小及布局、工具栏带中文提示 1.在vue.config.js 文件中添加 一下内容2.下载安装插件3.在Editor组件中引入插件4.使用Editor组件&#xff08;特别注意要的加 v-if &#xff09;5.bug 之 imageResize的 img的style丢失1.先创建一…

不是所有洗碗机都能空气除菌 友嘉灵晶空气除菌洗碗机评测

精致的三餐让你以为生活是“享受”&#xff0c;可饭后那些油腻的锅碗瓢盆却成了你我美好生活的最大障碍。想要只吃美食不洗碗&#xff0c;那一台优秀的洗碗机就必不可少了&#xff01;今天&#xff0c;ZOL中关村在线要评测的就是这样一台不光洗得干净更能有效除菌抑菌的洗碗机—…

SpringBoo+vue3+vite整合讯飞星火3.5通过webscoket实现聊天功能(前端代码)附带展示效果

访问地址&#xff1a; 天梦星服务平台 (tmxkj.top)https://tmxkj.top/#/site 后端文档&#xff1a; SpringBoovue3整合讯飞星火3.5通过webscoket实现聊天功能&#xff08;全网首发&#xff09;附带展示效果_springboot websocket vue3-CSDN博客https://blog.csdn.net/qq_53722…