
Qt中的事件(event)
事件(event)由視窗系統或Qt自身産生,用以響應所發生的各類事情。例如:當使用者按下鍵盤或者點選滑鼠上的按鍵時,就是産生一個鍵盤或者滑鼠事件;當某個視窗第一次顯示的時候,就會産生一個繪制事件,告知視窗繪制它本身,使視窗可見。事件主要分兩種情況:
- 與使用者互動時發生,比如按下滑鼠(mousePressEvent),敲擊鍵盤(keyPressEvent)等。
- 系統内部自身産生,比如計時器事件(timerEvent)等。
QEvent類是所有事件類的基類,事件對象裡包含事件參數。在Qt中,事件作為一個對象,繼承自QEvent。
任何從QObject類派生的類的對象都可以通過QObject.event()函數來接收事件;事件産生時,Qt會建立一個合适的QEvent對象或其派生類對象,然後通過調用QObject類的event()函數将這個事件對象傳遞給特定的QObject對象或其派生類對象。
Qt 的主事件循環(QCoreApplication::exec())從事件隊列中擷取本地視窗系統事件,将它們轉化為 QEvents,然後将轉換後的事件發送給 QObjects。QObjects 通過調用它們的 QObjec.event() 函數接收事件。該函數可以在子類中重新實作,來處理自定義的事件以及添加額外的事件類型,QWidget.event() 就是一個很好的例子。
Qt中的事件類
Qt中的常見事件類型:
- 鍵盤事件:處理鍵盤相關事件,比如按鍵等;
- 滑鼠事件:處理滑鼠相關事件,比如滑鼠按下,釋放,輕按兩下等事件;
- 拖放事件:拖放事件處理;
- 定時器事件:處理定時器事件;
- 視窗相關事件:處理視窗繪制,移動,改變尺寸,關閉,右鍵菜單等事件。
事件分發event()函數
事件的分發函數稱為事件處理器(event handler)。event()函數就是用來處理事件的分發。如果想在事件的分發之前進行一些操作,比如監聽某個按鍵的按下,就可以在event()函數裡處理:
class MyWidget(QWidget): ...... def event(e): if e.key() == Qt.Key_Tab: print('按下了Tab鍵') return True #按原來的流程來處理事件的分發 return QWidget.event(self, e)
在上面的代碼中,MyWidget是QWidget的子類,它重新實作了event()函數,該函數帶有一個QEvent類型的參數。當系統産生QEvent對象時,就會傳入這個函數并調用。如果傳入的事件被識别并處理,則傳回True,表示這個事件已經處理完畢,Qt不會将這個事件再分發出去。否則,就繼續将該事件分發出去。
Qt系統處理事件時,使用了一種機制,叫做事件傳播機制。意思是在子類(比如說一個按鈕QPushButton)中發生的事件,調用了子元件的event()函數之後,還會調用父元件(比如說QAbstractButton)的event()函數。event函數的傳回值就用于控制這一傳播過程。
Qt中的事件傳遞過程
事件過濾器
在一些應用場景中,需要攔截某個元件發生的事件,讓這個事件不再向其他元件傳播,這是可以為這個元件或其父元件安裝一個事件過濾器(eventFilter)來實作,在實際使用中。需要調用函數installEventFilter()為元件安裝過濾器,才能使用事件過濾器這個機制。安裝好事件過濾器之後,該元件和其子元件的事件就會被監聽。然後重寫其eventFilter函數,實作事件過濾。
QEvent中的成員函數
- ignore(): 接收者忽略目前接收到的事件,但事件可能會傳遞給接收者的父元件;
- accept(): 接收者期望處理當然事件;
- isAccept(): 判斷目前事件是否被處理;
- registerEventType(): 注冊并傳回一個自定義事件類型;
- spontaneous(): 如果事件由應用程式之外産生,比如一個系統事件,傳回True,否則傳回False;
- type(): 傳回事件的類型。QEvent.Type定義了Qt中有效的事件類型。
處理Qt事件的思路
在處理Qt的事件時,一般按照以下思路進行:
- 重寫paintEvent、mousePressEvent等事件處理函數。這是最常見、最簡單的方式;
- 重寫event函數。 event函數是所有對象的事件入口,在QObject和QWidget中的實作中,預設是把事件傳遞給特定的事件處理函數;
- 在特定對象上面安裝事件過濾器。該過濾器僅過濾該對象接收到的事件;
- 在QCoreApplication::instance()上面安裝事件過濾器。該過濾器将過濾所有對象的所有事件;
- 重寫QCoreApplication::notify()函數。這是最強大的,和全局事件過濾器一樣提供完全控制,并且不受線程的限制。
簡單示例代碼
示例代碼示範了對一些基本事件的處理,完整代碼如下:
import sysfrom PyQt5 import QtCore, QtGui, QtWidgetsfrom PyQt5.QtCore import Qt, QEvent, QTimerfrom PyQt5.QtGui import QPainterfrom PyQt5.QtWidgets import (QApplication, QWidget, QMenu, QMessageBox) class DemoEvent(QWidget): def __init__(self, parent=None): super(DemoEvent, self).__init__(parent) # 設定視窗标題 self.setWindowTitle('實戰PyQt5: QEvent事件示範') # 設定視窗大小 self.resize(400, 320) #初始化資料 self.doubleClicked = False #滑鼠輕按兩下 self.key='' #按鍵值 self.text='' #文本資訊 self.message='' #提示消息 #定時器,500毫秒後執行 QTimer.singleShot(500, self.onShowHelpInfo) def onShowHelpInfo(self): self.text = '點選這裡觸發追蹤滑鼠位置' #觸發重繪事件,即觸發paintEvent函數 self.update() #關閉應用時,會觸發closeEvent def closeEvent(self, event): if QMessageBox.information(self, '關閉應用', '點選按鈕關閉應用'): return True #右鍵菜單事件 def contextMenuEvent(self, event): #執行個體化一個菜單 menu = QMenu(self) actionTest1 = menu.addAction('測試1') actionTest1.triggered.connect(self.onMenuTest1) actionTest2 = menu.addAction('測試2') actionTest2.triggered.connect(self.onMenuTest2) if not self.message: menu.addSeparator() actionTest3 = menu.addAction('測試3') actionTest3.triggered.connect(self.onMenuTest3) #在滑鼠出現的位置顯示菜單欄 menu.exec(event.globalPos()) def onMenuTest1(self): self.message = '菜單選項1' self.update() def onMenuTest2(self): self.message = '菜單選項2' self.update() def onMenuTest3(self): self.message = '菜單選項3' self.update() #重繪視窗事件 def paintEvent(self, event): text = self.text pos = text.find('') if pos >= 0: text = text[0:pos] #如果觸發了鍵盤按鍵,則在文本資訊中記錄相應的按鍵資訊 if self.key: text += '按下了: {0}'.format(self.key) painter = QPainter(self) painter.setRenderHint(QPainter.TextAntialiasing) #居中繪制文本資訊 painter.drawText(self.rect(), Qt.AlignCenter, text) #如果有消息文本,則在底部居中繪制消息文本,3秒鐘口消息文本清空重繪 if self.message: #顯示消息文本 painter.drawText(self.rect(), Qt.AlignBottom|Qt.AlignLeft, self.message) #3秒後觸發清空資訊的函數,并重繪事件 QTimer.singleShot(3000, self.clearMessage) def clearMessage(self): self.message = '' self.update() #滑鼠釋放事件 def mouseReleaseEvent(self, event): #如果是輕按兩下釋放,就不跟蹤滑鼠移動 if self.doubleClicked: self.doubleClicked = False else: self.setMouseTracking(not self.hasMouseTracking()) if self.hasMouseTracking(): self.text = '開啟滑鼠位置跟蹤功能.' + '請移動一下滑鼠!!!' + '單擊滑鼠可以關閉這個功能' else: self.text = '閉滑鼠跟蹤功能.' + '單擊滑鼠可以開啟這個功能' self.update() #滑鼠移動事件 def mouseMoveEvent(self, event): if not self.doubleClicked: #視窗坐标轉換為螢幕坐标 globalPos = self.mapToGlobal(event.pos()) self.text = '滑鼠位置: 視窗坐标為:QPoint({0}, {1}) 螢幕坐标為:QPoint({2}, {3})' .format(event.pos().x(), event.pos().y(), globalPos.x(), globalPos.y()) self.update() #滑鼠輕按兩下事件 def mouseDoubleClickEvent(self, event): self.doubleClicked = True self.text = '你輕按兩下了滑鼠' self.update() #鍵盤按鍵事件 def keyPressEvent(self, event): self.key = '' if event.key() == Qt.Key_Home: self.key = 'Home' elif event.key() == Qt.Key_End: self.key = 'End' elif event.key() == Qt.Key_PageUp: if event.modifiers() & Qt.ControlModifier: self.key = "Ctrl+PageUp" else: self.key = "PageUp" elif event.key() == Qt.Key_PageDown: if event.modifiers() & Qt.ControlModifier: self.key = "Ctrl+PageDown" else: self.key = "PageDown" elif Qt.Key_A <= event.key() <= Qt.Key_Z: if event.modifiers() & Qt.ShiftModifier: self.key = "Shift+" self.key += event.text() #如果key有字元,不為空,則繪制字元 if self.key: self.update() #否則就繼續監視這個事件 else: QWidget.keyPressEvent(self, event) ''' 重新實作其他事件,适用于PyQt沒有提供該事件的處理函數的情況, Tab鍵由于涉及焦點切換,不會傳遞給keyPressEvent, 是以,需要在這裡重新定義。 ''' def event(self, event): #如果有按鍵按下,并且按鍵是tab鍵 if (event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab): self.key = "在event()中捕獲Tab鍵" self.update() return True return QWidget.event(self, event) if __name__ == '__main__': app = QApplication(sys.argv) window = DemoEvent() window.show() sys.exit(app.exec())
運作效果如下圖:
QEvent事件示範
本文知識點
- Qt的事件傳播和處理機制;
- Qt中的各種事件;
- 事件發生時,會生成一個QEvent對象,需要even函數進行分發,來調用相應的事件處理器;
- Qt事件可能在處理後傳遞給父元件對象;
- 事件過濾器(evenFilter)可以令事件進行攔截,阻止其傳播,進而實作某些功能。
喜歡本文内容就關注, 收藏,點贊,評論和轉發。