添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

一、QWidget

QWidget 既是 QObject 的子类,也是 QPaintDevice 的子类!

而 QPainter 类实例,都要有一个目标 device!

TLW(Top Level Widget)顶层窗口,可以看作是 “没有 parent 的 widget” 或者 “widget.window() == self 的 widget”。当调用 widget.show() 的时候,tlw 作为一个独立窗口显示在屏幕上,non top level widget 则作为一个内部控件显示在某个 tlw 中。

1. QWidget.update(rect) 底层逻辑(源码分析)

  • QWidget.update(rect) 调用私有类的方法 QWidgetPrivate.update(rect)
  • QWidgetPrivate.update(rect) 首先判断 widget 是否需要绘制。如果不需要,则直接返回。比如 widget 不可见、update 被禁用、参数 rect 与 widget.rect 无交集,这些都不需要绘制。如果已经正在绘制过程中,就向程序全局 QCoreApplication 发布一个 UpdateLaterEvent 事件并返回。如果需要绘制,则在顶层窗口(TLW: Top Level Widget),将该 widget 需要绘制的 clipped 区域标记为脏。 tlwExtra→repaintManager→markDirty(clipped, q)
  • QWidgetRepaintManager::markDirty() 中。
    首先需要判断该 widget 是否在顶层窗口 tlw 中显示,如果 widget 与当前 tlw 没有联系,那么连标脏都不需要了,直接丢弃。
    然后将 widget 以及需要重绘的区域加入 tlw 的 dirtyWidgets 列表( addDirtyWidget(widget, r) )。顶层窗口 tlw 发布重绘请求 sendUpdateRequest(tlw)。
  • 在 sendUpdateRequest() 中,实际是向程序全局 QCoreApplication 发布一个 QEvent::UpdateRequest 事件。
  • 接着就进入了 Qt 的事件响应机制,由 QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e) 负责将 QEvent::UpdateRequest 事件交给顶层窗口 tlw (我猜的)的 QWidget.event() 分发处理。
  • QWidget.event() 调用 QWidgetPrivate::syncBackingStore() 处理 QEvent::UpdateRequest 事件。
  • QWidgetPrivate::syncBackingStore() >> QWidgetRepaintManager::sync() >> QWidgetRepaintManager::paintAndFlush() 负责最终的 paint( QWidgetPrivate::drawWidget() ) 与 flush( QWidgetRepaintManager::flush() )。我理解的是,paint 是指在“Qt 画布”上绘制出图像,flush 是指将内存画布上的图像冲洗到“显卡画布”上(flush 就跟各操作系统的显示驱动有关了)。
  • QWidgetPrivate::drawWidget() 方法中,主要会执行三个操作:① 绘制背景 paintBackground(painter, region, flags) ;② 调用 paintEvent() (通过发布 QEvent::Paint 事件);③ 调用 paintSiblingsRecursive() 递归绘制 child widgets。
  • 最后, QWidgetRepaintManager::flush() 调用 QtSrc/plugins/platforms/xxxos/xxxBackingStore::flush() 将内存画布冲印到显卡。
  • 参考: https://www.cnblogs.com/appsucc/p/14528310.html

    3、 QWidget.paintEvent()

    所有 QWidget 绘制的时候,都是调用 paintEvent() 方法进行绘制。不管是 QWidget.upadte() 还是 QWidget.repaint() ,都是要调用 paintEvent()

    如果继承自某个内建 widget(比如 QLabel, QPushButton 这些),那么重写该方法将会覆盖父类的绘制行为。

    此外!!!如果该 widget 还有 child widgets 的话,在执行完自己的 paintEvent() 之后,还会接着自动调用所有 child widgets 的 paintEvent()!

    (其实并不是所有,只需要调用与该 event.rect() 有交集的 child widgets 的 paintEvent() 方法。)

    4、 QWidget.render(painter, targetOffset, sourceRegion, renderFlags)

    先在临时画布上绘制 widget,截取出 sourceRegion 区域。然后在 target painter 上的 targetOffset 位置开始绘制。

    但是实际上 QWidget.render() 一直有个官方 BUG 未解决!( https://bugreports.qt.io/browse/QTBUG-26694)

    它实际绘制时候的 targetOffset,可能会变成是窗口(top-level widget)坐标系,而不是 painter.device() 指向的父 widget 坐标系。

    5、先清除,再绘制!

    QWidget.update(rect) 会将 rect 区域先清除,再绘制。( paintBackground() >> paintEvent() )

    Qt normally erases the widget's area before the paintEvent() call.

    二、QPainter

    1、QPainter 内部必须关联一个 QPaintDevice 对象(所有的 QWidget 都属于 QPaintDevice)。

    2、要使用 QPainter 对象,必须先激活它。QPainter 必须以 begin(paintDevice) 方法激活,或者由带 paintDevice 参数的构造函数自动激活。然后由 end() 方法注销,或者是由析构函数自动注销。

    3、 我认为,可以将 QPainter 看作内存画布。Qt 对 widgets 的绘制,是先从顶层窗口(TLW,Top Level Widget)进行绘制,然后一级一级往下绘制 child widgets。并且绘制子 widget 的时候,可以看作是先在另一个临时画布上绘制,然后再裁剪出所需要的区域 clipped,贴到父 widget 的画布上。最后,所有 widgets 都绘制完后,才将顶层窗口的画布 flush 到显卡上。

    三、QMovie - GIF

    1、 Qlabel.setMovie() 的时候会绑定 QMovie 的 update 信号到私有方法 Qlabel._q_movieUpdated。 然后在该方法中调用 QWidget.update(rect) 来进行重绘。

    2、而我们在上文说过, QWidget.update(rect) 方法不会立即绘制,而是先在顶层窗口 tlw 将该区域标记为脏。然后由 tlw 响应全局 UpdateRequest 事件,再一层一层往下传递绘制。

    假设此时的 QLabel 是属于某个 widget 的 child,然后这个 QLabel 最终的父 widget 是 None,无法追溯到 tlw,那么 tlw 就不会响应它的 update(rect) 事件。也就无法重绘 GIF 了!

    当然,如果此时你直接 QLabel.show(),那么它自己就会作为一个 tlw 显示在屏幕上,这样就可以触发对 update 的响应了。