PyQt重绘事件处理函数paintEvent


PyQt 中的重绘和Windows 编程中的重绘差不多,但是Qt 的重绘更有特色,更加智能。基础部件类QWidget 提供的paintEvent 函数是一个纯虚函数,继承它的子类想用它,就必须重新实现它。下列4 种情况会发生重绘事件:

1 )当窗口部件第一次显示时,系统会自动产生一个绘图事件。

2 repaint() update() 函数被调用时。

3 )当窗口部件被其他部件遮挡,然后又再次显示出来时,就会对隐藏的区域产生一个重绘事件。

4 )重新调整窗口大小时。

paintEvent() 是一个虚函数槽,子类可以对父类的paintEvent 进行重写。当调用update() repaint() 的时候,paintEvent() 会被调用,另外,当界面有任何改变的时候,paintEvent() 也会被调用,这种界面的改变包括界面从隐藏到显示,界面尺寸改变,当然界面内容改变的时候也会被调用。paintEvent() 是已经被高度优化过的函数,它本身已经自动开启并实现了双缓冲(X11 系统需要手动开启双缓冲),因此Qt 中重绘不会引起任何闪烁。有了paintEvent 的知识,现在再来看看update() repaint() update() repaint() 是一类的,需要重绘的对象主动去调用,然后重绘。update() repaint() 调用之后,都会调用paintEvent().repaint() ,被调用之后立即执行重绘,因此repaint() 是最快的,紧急情况下需要立刻重绘的可以使用repaint() 。但是调用repaint() 的函数不能放到paintEvent 中调用。举个例子:有一个继承自QWidget 的子类MyWidget ,在子类中对paintEvent 进行重写。我们在MyWidget::myrepaint() 中调用repaint() 。但是,myrepaint() 又被重写的paintEvent() 调用。这样调用repaint() 的函数又被paintEvent() 调用,由于repaint() 是立即重绘,而且repaint() 在调用paintEvent 之前几乎不做任何优化操作,而会造成死循环,即先调用repaint() ,继而调用paintEvent() paintEvent() 反过来又调用repaint()... ,如此造成死循环。update() repaint() 比较,update() 更加有优越性。update() 调用之后并不是立即重绘,而是将重绘事件放入主消息循环中,由main() event loop 来统一调度(其实也是比较快的)。update() 在调用paintEvent() 之前还做了很多优化,如果update() 被调用了很多次,最后这些update() 会合并到一个大的重绘事件加入消息队列中,最后只有这个大的update() 被执行一次。同时也避免了repaint() 中所提到的死循环。因此,一般情况下,我们调用update() 就够了,跟repaint() 比起来,update() 是推荐使用的。

打个比方,QPainter 相当于Qt 中的画家,能够绘制各种基础图形,拥有绘图所需的画笔、画刷、字体。绘图常用的工具画笔类QPen 、画刷类QBrush 和字体类QFont 都继承自QPainter QPen 用于绘制几何图形的边缘,由颜色、宽度、线风格等参数组成;QBrush 用于填充几何图形的调色板,由颜色和填充风格组成;QFont 用于文本绘制,由字体属性组成。

QPaintDevice 相当于Qt 中的画布、画家的绘图板,所有的QWidget 类都继承自QPaintDevice 。通常我们把绘图操作只需放在paintEvent 函数中即可。在QWidget 类中,paintEvent 的声明如下:

def paintEvent(self, a0: QtGui.QPaintEvent) -> None: ...

我们只需在QWidget 的子类中重写paintEvent 方法来实现画图,即把绘图函数放在paintEvent 中调用,比如:

def paintEvent(self, evt):
    painter = QPainter(self)
    painter.drawLine(0, 0, 100,50); #画线函数

现在不熟悉这些绘图函数没关系,后面会详述,不过我们可以来看其效果。

【例8.1 】第一个PyQt 画图程序

1 )启动PyCharm ,新建一个工程,工程名是PythonProject

2 )启动Qt Designer ,新建一个Dialog without Buttons 对话框。从控件工具箱中拖拉一个按钮到对话框上,然后添加clicked 信号的槽函数onc1 。把这个界面设计的结果保存到mydlg.ui 文件中,关闭Qt Designer 。为了节省篇幅,在本章下面的实例中,这个过程就不再赘述了,代码也只演示paintEvent 中的代码。

3 )回到PyCharm ,转换mydlg.ui 文件,在main.py 中添加如下代码:

import sys
from PyQt5.QtGui import QPainter, QColor
from mydlg import Ui_Dialog
from PyQt5.QtWidgets import *
 
class CMainDlg(QDialog, Ui_Dialog):
    def __init__(self):
        super(CMainDlg, self).__init__()
        self.clr=255;
        self.setupUi(self)
 
    def paintEvent(self, evt):
        painter = QPainter(self)
        color = QColor()             #建立一个颜色对象
        color.setRed(self.clr)       #把颜色设为红色
        painter.setPen(color);       #设置画笔的颜色
        w = self.size().width();    #获取窗口宽度
        h = self.size().height();   #获取窗口高度
        painter.drawLine(0, 0, w // 2, h);  #画线函数
        painter.drawLine(w // 2, h,w,0);     #画线函数
 
    def onc1(self):         #按钮的clicked信号的槽函数
        if self.clr==255:
            self.clr=0;       #黑色
        else:
            self.clr=255;    #红色
        self.update();       #更新窗口,此时将触发paintEvent函数的自动调用
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = CMainDlg()
    window.show()
    sys.exit(app.exec())

paintEvent 函数中,我们定义了一个颜色对象color ,并通过成员变量self.clr 来设置具体的颜色值,然后通过setPen 函数设置画笔的颜色。接着,获取对话框的客户区的宽度和高度,最后调用画线函数drawLine 来画两条线。只要窗口或部件需要被重绘,paintEvent 函数就会被调用。每个要显示输出的窗口部件都必须实现它。为了在窗口重绘时能显示我们绘制的图形,所以要把绘图函数放在paintEvent 中。而在按钮的clicked 信号的槽函数中,我们设置了不同的self.clr 值,最后使用update 函数更新窗口,此时将自动触发paintEvent 函数的调用。

4 )按【Shift+F10 】快捷键运行工程,运行结果如图8-1 所示。

8-1

本文节选自《 PyQt 5 从入门到精通》,内容发布获得作者和出版社授权。

 


请使用浏览器的分享功能分享到微信等