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從入門到精通》,内容釋出獲得作者和出版社授權。