Qt 界面重绘刷新机制

一、Qt 窗口绘制原理

最基本的原理是:绘制是在主线程中完成的。主线程是什么,可以理解为就是 main() 函数,main()函数最后需要调用 QApplication 的 exec() 函数,这个 exec() 里面是个死循环,每次循环调用一次 QApplication 的 processEvents(),这个函数负责处理窗口事件、用户消息,然后绘制窗口。就是说所有这些事都是在主线程中依次完成的,并不是同时进行,所以如果你在主线程中处理数据(比如响应按钮事件),你这个事件处理函数不返回,就不会往下走到绘制这一步。

窗口系统为了提高性能,会尽量减少绘制次数和范围,所以默认情况下,只有窗口内容显示出来的时候才进行一次绘制,包括窗口被遮挡的部分重新显现。

update 和 repaint 就是主动通知系统绘制窗口内容。区别在于:

  • update 只是在内部数据中做一个标记,要等到主线程进行到下一次绘制时才进行绘制,所以多次调用 update 是没有意义的,只是重复设置绘制标记,并不会马上进行绘制,而且同样要等到你的代码走完之后走到绘制时才能看到结果。
  • repaint虽然可以立即调用到 paintEvent,但是光有这个 paintEvent 还不够,不跑完整个 processEvents,缺少了界面响应,你的窗口就是个死的,看上去仍然不是实时显示。

二、paintEvent 绘图事件

paintEvent 是一个虚函数槽,子类可以对父类的 paintEvent 进行重写。paintEvent 是已经被高度优化过的函数,它本身已经自动开启并实现了双缓冲(X11 系统需要手动去开启双缓冲),因此 Qt 中重绘不会引起任何闪烁。paintEvent 方法是进行重绘的,只要出现以下几种情况,系统就会自动调用 paintEvent 方法:

  • 调用 update 或者 repaint 来产生一个绘图事件;
  • 当窗口第一次显示时,系统会自动产生绘图事件;
  • 窗口大小(重新调整)改变,或者重新排布(布局)导致重画;
  • 窗口显隐导致重画;
  • 当窗口部件被其他部件遮挡时,然后又再次显示出来,会对隐藏区域进行重绘事件。

绘制事件还有一点也很需要注意,那就是:当绘制事件发生的时候,更新区域通常被擦除。如果需要在上次绘制的基础上进行绘制的话,我的做法是:用一个临时变量存着上次绘制后的图,然后在这个图上进行绘制,最后再直接显示这个图就行了。通过 QPaintEvent::erased 可以得知这个窗口部件是否被擦除(写完查了一下,设置 WRepaintNoErase 窗口部件标记被设置的时候不会擦除)。编写画图板应用时,尤其注意这点,否则上次绘制好的图,下次绘制事件发生又擦除了。

repaint

repaint 调用之后,立即执行重绘,因此 repaint 是最快的,紧急情况下需要立刻重绘的可以使用 repaint。但是调用 repaint 的函数不能放到 paintEvent 中调用。

update

update 跟 repaint 比较,update 则更加有优越性。update 调用之后并不立即重绘,而是发送一个重绘事件,并把它放入事件队列,等到下次事件循环的时候,再处理重绘事件。此外,update 还做了一些优化,如果 update 在一个事件循环内被调用多次,这些 update 会被合并成一个大的重绘事件加入到事件队列,下次刷新的时候,只需要执行一次重绘。因此,一般情况下,我们调用 update 就够了,跟 repaint 比起来,update 是推荐使用的。

三、子窗口与父窗口的重绘关系

子窗口调用 update、repaint 等函数时,也会造成父窗口也重绘(即调用 paintEvent)。当是嵌入式板子时,主窗口就是最顶层的全屏窗口时,当子窗口是绘制视频窗口时,子窗口调用 update 更新绘图,全屏父窗口(就是主窗口)也会重绘。尤其是 Qt 绘制大面积区域很消耗 CPU 的情况下,这样 CPU 占有率就会比较高,Qt 鼠标移动也会比较卡顿(Qt 鼠标光标绘制也是 UI 主线程负责绘制的)。

如果嵌入式板子没有单独的鼠标层,鼠标是跟 UI 是在同一层,所以会发现快速移动鼠标,CPU 占有率会比较高。

四、QApplication::processEvents

QApplication::processEvents()使 Qt 现在就去更新界面各个控件的大小等等,完成界面刷新。但是这时候你操作了界面,比如按下了某个下拉按钮,这时候你的后台动作就会被暂停,直到界面下拉按钮全都缩回去之后,你的后台动作才会继续接着执行。

等于是在程序执行的过程中边执行,边抽取一点时间去更新界面。从而使得下面的一些函数立即得到执行。

QApplication::processEvents(); //让界面可以及时更新

如果你后面有个写文件很费时的操作,这样一来用户界面可以变化,支持用户继续操作;但是用户可能又点击了保存文件的操作。这时候你可以调用另一个来让用户不假死,但是不接受用户输入(你对界面点了也白点):

QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); // 它可以忽略用户的输入(鼠标和键盘事件)。

posted @ 2023-02-22 17:45  fengMisaka  阅读(2430)  评论(0编辑  收藏  举报