PyQt5系列教程(5):事件與信號處理
上回我們做了一個猜數字的小遊戲,涉及到了信號與槽的知識,今天我們重點的講解一下這方面的知識!
---------------------------------------------------------華麗的分割線-------------------------------------------------------
GUI應用程序是事件驅動的。 事件主要由應用程序的用戶生成。 但它們也可以通過其他手段產生,例如:網路連接,窗口管理器或定時器。 當我們調用應用程序的exec_()方法時,應用程序進入主循環。 主循環獲取事件並將其發送到對象。
在事件模型中,有三個參與者:
- 事件來源
- 事件對象
- 事件目標
事件源是其狀態更改的對象。 它會生成事件。 事件對象(event)將狀態更改封裝在事件源中。 事件目標是要通知的對象。 事件源對象將處理事件的任務委託給事件目標。
PyQt5具有獨特的信號和插槽機制來處理事件。 信號和槽用於對象之間的通信。 發生特定事件時發出信號。 槽可以是任何Python可調用的函數。 當發射連接的信號時會調用一個槽。
---------------------------------------------------------華麗的分割線-------------------------------------------------------
- 簡單的信號與槽示例
代碼如下:
#coding=utf-8nnimport sysnfrom PyQt5.QtCore import Qtnfrom PyQt5.QtWidgets import (QWidget, QLCDNumber, QDial, QApplication)nnclass Example(QWidget):n def __init__(self):n super().__init__()n self.initUi()nn def initUi(self):n lcd = QLCDNumber(self)n dial = QDial(self)nn self.setGeometry(300, 300, 350, 250)n self.setWindowTitle(學點編程吧)nn lcd.setGeometry(100,50,150,60)n dial.setGeometry(120,120,100,100)nn dial.valueChanged.connect(lcd.display)nn self.show()nnif __name__ == __main__:n app = QApplication(sys.argv)n ex = Example()n sys.exit(app.exec_())n
這個例子的執行結果如下:
在這個例子我們展示了一個QtGui.QLCDNumber和一個QtGui.QDial這個兩個小部件,當我們撥動QDial這個小部件的時候,LCD屏幕就會顯示出此時Dial小部件的值。
dial.valueChanged.connect(lcd.display)n
這裡我們將QDial這個小部件的一個valueChanged信號連接到lcd數字的顯示槽。
QDial對象發送信號。 QLCDNumber接收信號的。 槽是對信號作出反應的方法。
當然你把上面的QDial小部件換成QSlider,也是一樣的效果,執行結果如下:
代碼請自行參照修改,這裡就不再做講解了。
---------------------------------------------------------華麗的分割線-------------------------------------------------------
- 重新實現事件處理程序
下面這個例子,我們就重新實現了按下按鈕後要如何處理。
#coding=utf-8nnimport sysnfrom PyQt5.QtCore import Qtnfrom PyQt5.QtWidgets import (QWidget, QApplication, QLabel)nnclass Example(QWidget):n def __init__(self):n super().__init__()n self.initUi()n def initUi(self):n self.setGeometry(300, 300, 350, 250)n self.setWindowTitle(學點編程吧)nn self.lab = QLabel(方向,self)nn self.lab.setGeometry(150,100,50,50)nn self.show()nn def keyPressEvent(self, e):nn if e.key() == Qt.Key_Up:n self.lab.setText(↑)n elif e.key() == Qt.Key_Down:n self.lab.setText(↓)n elif e.key() == Qt.Key_Left:n self.lab.setText(←)n else:n self.lab.setText(→)nnif __name__ == __main__:n app = QApplication(sys.argv)n ex = Example()n sys.exit(app.exec_())n
完成後的效果如下:
def keyPressEvent(self, e):nn if e.key() == Qt.Key_Up:n self.lab.setText(↑)n elif e.key() == Qt.Key_Down:n self.lab.setText(↓)n elif e.key() == Qt.Key_Left:n self.lab.setText(←)n else:n self.lab.setText(→)n
在我們的例子中,我們重新實現了keyPressEvent()事件處理程序。當我們按住上、下、左、右方向鍵的時候,窗口中依次會出現對應方位。
關注微信公眾號:學點編程吧,發送pyqt55d可以獲得全部Qt按鍵代號。
我們再舉一個重寫滑鼠事件與繪圖事件的例子:
#coding=utf-8nnimport sysnfrom PyQt5.QtWidgets import (QApplication, QLabel, QWidget)nfrom PyQt5.QtGui import QPainter, QColor, QPennfrom PyQt5.QtCore import Qtnnclass Example(QWidget):n distance_from_center = 0n def __init__(self):n super().__init__()n self.initUI()n self.setMouseTracking(True)n def initUI(self):n self.setGeometry(200, 200, 1000, 500)n self.setWindowTitle(學點編程吧)n self.label = QLabel(self)n self.label.resize(500, 40)n self.show()n self.pos = Nonenn def mouseMoveEvent(self, event):n distance_from_center = round(((event.y() - 250)**2 + (event.x() - 500)**2)**0.5)n self.label.setText(坐標: ( x: %d ,y: %d ) % (event.x(), event.y()) + " 離中心點距離: " + str(distance_from_center)) n self.pos = event.pos()n self.update()nn def paintEvent(self, event):n if self.pos:n q = QPainter(self)n q.drawLine(0, 0, self.pos.x(), self.pos.y())nnif __name__ == __main__:n app = QApplication(sys.argv)n ex = Example()n sys.exit(app.exec_())n
執行的結果如下:
在這個例子中我們實現了滑鼠坐標(x,y)的獲取,以及繪製一條線,這條線的起點坐標在(0,0),另外一個端點隨滑鼠移動而移動,同時我們還要計算滑鼠坐標與中心點的距離(運用勾股定理進行計算)
self.setMouseTracking(True)n
默認情況下禁用滑鼠跟蹤, 如果啟用滑鼠跟蹤,即使沒有按鈕被按下,小部件也會接收滑鼠移動事件。當然你也可以不寫,只需要在執行的過程中按照滑鼠左鍵也行。
def mouseMoveEvent(self, event):n distance_from_center = round(((event.y() - 250)**2 + (event.x() - 500)**2)**0.5)n self.label.setText(坐標: ( x: %d ,y: %d ) % (event.x(), event.y()) + " 離中心點距離: " + str(distance_from_center)) n self.pos = event.pos()n self.update()n
這個函數就是捕捉滑鼠移動事件了,我們把得到的坐標已經一些相關的信息顯示在label上。必須調用函數update()才能更新圖形。
def paintEvent(self, event):n if self.pos:n q = QPainter(self)n q.drawLine(0, 0, self.pos.x(), self.pos.y())n
繪圖的話需要重寫繪圖事件,我們生成QPainter對象,然後調用drawLine()方法繪製一條線,需要四個參數,起點的坐標,終點的坐標。後面會詳細的對繪圖進行舉例,這裡只是為了配合滑鼠移動事件,做一個例子。
---------------------------------------------------------華麗的分割線-------------------------------------------------------
- 事件發送者
有時,知道哪個窗口小部件是信號的發送者非常有用。 為此,PyQt5具有sender()方法。例如下面這個例子,我們實現了簡單的石頭、剪刀、布的小遊戲。
#coding=utf-8nnimport sysnfrom PyQt5.QtWidgets import (QApplication, QMessageBox, QWidget, QPushButton)nfrom random import randintnnclass Example(QWidget):nn def __init__(self):n super().__init__()n self.initUI()n def initUI(self):n self.setGeometry(200, 200, 300, 300)n self.setWindowTitle(學點編程吧)nn bt1 = QPushButton(剪刀,self)n bt1.setGeometry(30,180,50,50)nn bt2 = QPushButton(石頭,self)n bt2.setGeometry(100,180,50,50)nn bt3 = QPushButton(布,self)n bt3.setGeometry(170,180,50,50)nn bt1.clicked.connect(self.buttonclicked)n bt2.clicked.connect(self.buttonclicked)n bt3.clicked.connect(self.buttonclicked)nn self.show()nn def buttonclicked(self):n computer = randint(1,3)n player = 0n sender = self.sender()n if sender.text() == 剪刀:n player = 1n elif sender.text() == 石頭:n player = 2n else:n player = 3nn if player == computer:n QMessageBox.about(self, 結果, 平手)n elif player == 1 and computer == 2:n QMessageBox.about(self, 結果, 電腦:石頭,電腦贏了!)n elif player == 2 and computer == 3:n QMessageBox.about(self, 結果, 電腦:布,電腦贏了!)n elif player == 3 and computer == 1:n QMessageBox.about(self,結果,電腦:剪刀,電腦贏了!)n elif computer == 1 and player == 2:n QMessageBox.about(self,結果,電腦:剪刀,玩家贏了!)n elif computer == 2 and player == 3:n QMessageBox.about(self,結果,電腦:石頭,玩家贏了!)n elif computer == 3 and player == 1:n QMessageBox.about(self,結果,電腦:布,玩家贏了!)nnif __name__ == __main__:n app = QApplication(sys.argv)n ex = Example()n sys.exit(app.exec_())n
執行的結果如下:
我們在我們的例子中有三個按鈕,分別代表石頭、剪刀、布。 在buttonClicked()方法中,我們通過調用sender()方法來確定我們點擊了哪個按鈕。
bt1.clicked.connect(self.buttonclicked)nbt2.clicked.connect(self.buttonclicked)nbt3.clicked.connect(self.buttonclicked)n
三個按鈕的clicked信號都連接到同一個槽buttonclicked
def buttonclicked(self):n computer = randint(1,3)n player = 0n sender = self.sender()n if sender.text() == 剪刀:n player = 1n elif sender.text() == 石頭:n player = 2n else:n player = 3nn if player == computer:n QMessageBox.about(self, 結果, 平手)n elif player == 1 and computer == 2:n QMessageBox.about(self, 結果, 電腦:石頭,電腦贏了!) n elif player == 2 and computer == 3:n QMessageBox.about(self, 結果, 電腦:布,電腦贏了!)n elif player == 3 and computer == 1:n QMessageBox.about(self,結果,電腦:剪刀,電腦贏了!)n elif computer == 1 and player == 2:n QMessageBox.about(self,結果,電腦:剪刀,玩家贏了!)n elif computer == 2 and player == 3:n QMessageBox.about(self,結果,電腦:石頭,玩家贏了!)n elif computer == 3 and player == 1:n QMessageBox.about(self,結果,電腦:布,玩家贏了!)n
我們通過調用sender()方法來確定信號源,根據信號源確定玩家究竟選擇了石頭、剪刀、布中的哪一個。 從而與電腦隨機給出的數字進行比較,判斷輸贏。
---------------------------------------------------------華麗的分割線-------------------------------------------------------
- 發出自定義信號
從QObject創建的對象可以發出信號。 以下示例顯示了我們如何發出自定義信號。
#coding=utf-8nnimport sysnfrom PyQt5.QtWidgets import (QApplication, QWidget, QMessageBox)nfrom PyQt5.QtCore import (pyqtSignal, QObject)nnclass Signal(QObject):n showmouse = pyqtSignal()nnclass Example(QWidget):nn def __init__(self):n super().__init__()n self.initUI()n def initUI(self):n self.setGeometry(200, 200, 300, 300)n self.setWindowTitle(學點編程吧)nn self.s = Signal()n self.s.showmouse.connect(self.about)nn self.show()n def about(self):n QMessageBox.about(self,滑鼠,你點滑鼠了吧!)nn def mousePressEvent(self, e):n self.s.showmouse.emit()nnif __name__ == __main__:n app = QApplication(sys.argv)n ex = Example()n sys.exit(app.exec_())n
在這個例子當中,當我們單擊滑鼠的時候,就會彈出對話框告知我們單擊了滑鼠。執行結果如下:
我們創建一個名為showmouse的新信號。 該信號在滑鼠按壓事件期間發出。 該信號連接到QMainWindow的about()的槽。
class Signal(QObject):n showmouse = pyqtSignal()n
使用pyqtSignal()作為外部Signal類的類屬性創建一個信號。
self.s = Signal()nself.s.showmouse.connect(self.about)n
自定義showmouse信號連接到QMainWindow的about()的槽。
def mousePressEvent(self, e):n self.s.showmouse.emit()n
當我們用滑鼠指針點擊窗口時,會發出showmouse信號,調用相應的槽函數。
---------------------------------------------------------華麗的分割線-------------------------------------------------------
最後我們總結一下今天的內容:
- 信號與槽連接
- 事件處理重寫
- 事件發送者
- 發出自定義信號
ok,今天就到這裡,我們下期再約。
如果你想要本次教程中的相關源碼,請關注微信公眾號:學點編程吧,發送pyqt55,會自動得到相應的百度網盤下載鏈接。
推薦閱讀:
※Python從零開始系列連載(19)——Python特色數據類型(列表)(下)
※Python入門 數據結構 dict字典
※17個新手常見Python運行時錯誤
※如何系統的自學Python?