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从入门到精通》,内容发布获得作者和出版社授权。