python GUI庫圖形界面開發(fā)之PyQt5信號與槽事件處理機制詳細介紹與實例解析
PyQt5中信號與槽可以說是對事件處理機制的高級封裝,如果說事件是用來創(chuàng)建窗口控件的,那么信號與槽就是用來對這個控件進行使用的,比如一個按鈕,當我們使用按鈕時,只關(guān)心clicked信號,至于這個按鈕如何接受并處里鼠標點擊事件,然后在發(fā)射這個信號,則不關(guān)心,但是如果要重載一個按鈕,這時候就要關(guān)心了,比如可以改變它的行為:在鼠標按下時觸發(fā)clicked信號,而不是釋放時
PyQt5常見事件類型
pyqt是對Qt的封裝,qt程序是事件驅(qū)動的,它的每個動作都有幕后某個事件所觸發(fā),Qt事件類型有很多,常見的如下
- 鍵盤事件:按鍵的按下與松開
- 鼠標事件:鼠標指針的移動,鼠標按鍵的按下與松開
- 拖放事件:用鼠標進行拖放
- 滾輪事件:鼠標滾輪滾動
- 繪屏事件:重繪制屏幕的某些部分
- 定時事件:定時器到時
- 焦點事件:鍵盤焦點移動
- 進入和離開事件:鼠標指針移入Widget內(nèi),或者移出
- 移動事件:Widget的位置改變
- 大小改變事件:widget的大小改變
- 顯示和隱藏事件:widget顯示與隱藏
- 窗口事件:窗口是否為當前窗口
還有一些常見的qt事件,比如Socket事件,剪切板事件,字體改變事件,布局改變事件
使用事件處理的方法
pyqt提供如下5中事件處理和過濾的方法(有弱到強),其中只有前兩種方法使用最頻繁
1 、重新實現(xiàn)事件函數(shù)
比如mousePressEvent(),keyPressEvent(),paintEvent(),這是最常規(guī)的事件處理方法
2 、重新實現(xiàn)QObject.event()
一般用在pyqt沒有提供該事件的處理函數(shù)的情況下,即增加新事件時
3 、安裝事件過濾器
如果對QObject調(diào)用installEventFilter,則相當于為這個QObject安裝了一個事件過濾器,對于QObject的全部事件來說,它們都會先傳遞到事件過濾函數(shù)eventFilter中,在這個函數(shù)中,我們可以拋棄或者修改這些事件,比如對自己感興趣的事件使用自定義的處理機制,對其他事件采用默認的事件處理機制,由于這中方法會調(diào)用installEventFilter的所有QObject的事件進行過濾,因此如果要過濾的事件比較多,則會降低程序的性能
4 、在QApplication中安裝事件過濾器
這種方法比上一種更強大,QApplication的事件過濾器將捕獲所有的QObject事件,而且第一個獲得該事件,也就是說,在將事件發(fā)送給其他任何一個事件過濾器之前,都會發(fā)送給QApplication的事件過濾器
5 、重新實現(xiàn)QApplication的notify()方法
pyqt使用notify來分發(fā)事件,要想在任何事件處理器之前捕獲事件,唯一的方法就是重新實現(xiàn)QApplication的notify(),在實踐中,在調(diào)試才會用這中方法
PyQt5信號與槽事件處理經(jīng)典案例
import sys
from PyQt5.QtCore import (QEvent, QTimer, Qt)
from PyQt5.QtWidgets import (QApplication, QMenu, QWidget)
from PyQt5.QtGui import QPainter
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
#初始化數(shù)據(jù)
#鼠標雙擊False
self.justDoubleClicked = False
#按鍵,輸出文本,提示消息為空
self.key = ""
self.text = ""
self.message = ""
#設(shè)置窗口初始大小與位置
self.resize(400, 300)
self.move(100, 100)
#設(shè)置標題
self.setWindowTitle("Events")
#定時器1秒后執(zhí)行槽函數(shù)
QTimer.singleShot(1000, self.giveHelp)
# 避免窗口大小重繪事件的影響,可以把參數(shù)0改變成3000(3秒),然后在運行,就可以明白這行代碼的意思。
def giveHelp(self):
self.text = "請點擊這里觸發(fā)追蹤鼠標功能"
# 重繪事件,也就是觸發(fā)paintEvent函數(shù)。
self.update()
'''重新實現(xiàn)關(guān)閉事件'''
def closeEvent(self, event):
print("Closed")
'''重新實現(xiàn)上下文菜單事件'''
def contextMenuEvent(self, event):
#實例化菜單,添加子菜單one two并附加快捷鍵功能,關(guān)聯(lián)槽函數(shù)
menu = QMenu(self)
oneAction = menu.addAction("&One")
twoAction = menu.addAction("&Two")
oneAction.triggered.connect(self.one)
twoAction.triggered.connect(self.two)
#如果message為空,執(zhí)行
if not self.message:
#在菜單中添加一條分割線
menu.addSeparator()
#添加自菜單three,關(guān)聯(lián)槽函數(shù)
threeAction = menu.addAction("Thre&e")
threeAction.triggered.connect(self.three)
#菜單欄出現(xiàn)在鼠標的位置
menu.exec_(event.globalPos())
'''上下文菜單槽函數(shù)'''
def one(self):
self.message = "Menu option One"
self.update()
def two(self):
self.message = "Menu option Two"
self.update()
def three(self):
self.message = "Menu option Three"
self.update()
'''重新實現(xiàn)繪制事件'''
def paintEvent(self, event):
text = self.text
i = text.find("\n\n")
if i >= 0:
text = text[0:i]
# 若觸發(fā)了鍵盤按鈕,則在文本信息中記錄這個按鈕信息。
if self.key:
text += "\n\n你按下了: {0}".format(self.key)
painter = QPainter(self)
painter.setRenderHint(QPainter.TextAntialiasing)
# 繪制信息文本的內(nèi)容
painter.drawText(self.rect(), Qt.AlignCenter, text)
# 若消息文本存在則在底部居中繪制消息,5秒鐘后清空消息文本并重繪。
if self.message:
#顯示給定坐標處的文本,坐標,對齊方式。文本內(nèi)容
painter.drawText(self.rect(), Qt.AlignBottom | Qt.AlignHCenter,
self.message)
#5秒鐘后觸發(fā)清空信息的函數(shù),并重新繪制事件
QTimer.singleShot(5000, self.clearMessage)
QTimer.singleShot(5000, self.update)
'''清空消息文本的槽函數(shù)'''
def clearMessage(self):
self.message = ""
'''重新實現(xiàn)調(diào)整窗口大小事件'''
def resizeEvent(self, event):
self.text = "調(diào)整窗口大小為: QSize({0}, {1})".format(
event.size().width(), event.size().height())
self.update()
'''重新實現(xiàn)鼠標釋放事件'''
def mouseReleaseEvent(self, event):
# 若鼠標釋放為雙擊釋放,則不跟蹤鼠標移動
if self.justDoubleClicked:
self.justDoubleClicked = False
# 若鼠標釋放為單擊釋放,則需要改變跟蹤功能的狀態(tài),如果開啟跟蹤功能的話就跟蹤,不開啟跟蹤功能就不跟蹤
else:
# 單擊鼠標
self.setMouseTracking(not self.hasMouseTracking())
if self.hasMouseTracking():
self.text = "開啟鼠標跟蹤功能.\n" + \
"請移動一下鼠標!\n" + \
"單擊鼠標可以關(guān)閉這個功能"
else:
self.text = "關(guān)閉鼠標跟蹤功能.\n" + \
"單擊鼠標可以開啟這個功能"
self.update()
'''重新實現(xiàn)鼠標移動事件'''
def mouseMoveEvent(self, event):
#如果沒有鼠標雙擊,執(zhí)行
if not self.justDoubleClicked:
# 窗口坐標轉(zhuǎn)換為屏幕坐標
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()
'''重新實現(xiàn)鼠標雙擊事件'''
def mouseDoubleClickEvent(self, event):
self.justDoubleClicked = True
self.text = "你雙擊了鼠標"
self.update()
'''重新實現(xiàn)鍵盤按下事件'''
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.key = self.key
self.update()
#否則就繼續(xù)監(jiān)視這個事件
else:
QWidget.keyPressEvent(self, event)
'''重新實現(xiàn)其他事件,適用于PyQt沒有提供該事件的處理函數(shù)的情況,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)
form = Widget()
form.show()
app.exec_()
代碼解析
首先是類的建立,建立text和message兩個變量,使用painEvent函數(shù)把他們輸出到窗口中
update函數(shù)的作用是更新窗口,由于窗口更新過程中會觸發(fā)一次paineEvent函數(shù)(paintEvent是窗口基類QWidget的內(nèi)部函數(shù)),因此在本例中,update函數(shù)的作用等同于paintEvent函數(shù)
import sys
from PyQt5.QtCore import (QEvent, QTimer, Qt)
from PyQt5.QtWidgets import (QApplication, QMenu, QWidget)
from PyQt5.QtGui import QPainter
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
#初始化數(shù)據(jù)
#鼠標雙擊False
self.justDoubleClicked = False
#按鍵,輸出文本,提示消息為空
self.key = ""
self.text = ""
self.message = ""
#設(shè)置窗口初始大小與位置
self.resize(400, 300)
self.move(100, 100)
#設(shè)置標題
self.setWindowTitle("Events")
#定時器1秒后執(zhí)行槽函數(shù)
QTimer.singleShot(1000, self.giveHelp)
# 避免窗口大小重繪事件的影響,可以把參數(shù)0改變成3000(3秒),然后在運行,就可以明白這行代碼的意思。
def giveHelp(self):
self.text = "請點擊這里觸發(fā)追蹤鼠標功能"
# 重繪事件,也就是觸發(fā)paintEvent函數(shù)。
self.update()
初始化運行結(jié)果如下

然后是重新實現(xiàn)窗口關(guān)閉事件與上下文菜單事件,主要影響message標量的結(jié)果,paintEvent負責(zé)把這個變量在窗口底部輸出
'''重新實現(xiàn)關(guān)閉事件'''
def closeEvent(self, event):
print("Closed")
'''重新實現(xiàn)上下文菜單事件'''
def contextMenuEvent(self, event):
#實例化菜單,添加子菜單one two并附加快捷鍵功能,關(guān)聯(lián)槽函數(shù)
menu = QMenu(self)
oneAction = menu.addAction("&One")
twoAction = menu.addAction("&Two")
oneAction.triggered.connect(self.one)
twoAction.triggered.connect(self.two)
#如果message為空,執(zhí)行
if not self.message:
#在菜單中添加一條分割線
menu.addSeparator()
#添加自菜單three,關(guān)聯(lián)槽函數(shù)
threeAction = menu.addAction("Thre&e")
threeAction.triggered.connect(self.three)
#菜單欄出現(xiàn)在鼠標的位置
menu.exec_(event.globalPos())
'''上下文菜單槽函數(shù)'''
def one(self):
self.message = "Menu option One"
self.update()
def two(self):
self.message = "Menu option Two"
self.update()
def three(self):
self.message = "Menu option Three"
self.update()

繪制事件是代碼的核心事件,它的作用是時刻跟隨text和message這兩個變量的信息,并把text內(nèi)容繪制到窗口的中部,把message的內(nèi)容繪制到窗口的底部
'''重新實現(xiàn)繪制事件'''
def paintEvent(self, event):
text = self.text
i = text.find("\n\n")
if i >= 0:
text = text[0:i]
# 若觸發(fā)了鍵盤按鈕,則在文本信息中記錄這個按鈕信息。
if self.key:
text += "\n\n你按下了: {0}".format(self.key)
painter = QPainter(self)
painter.setRenderHint(QPainter.TextAntialiasing)
# 繪制信息文本的內(nèi)容
painter.drawText(self.rect(), Qt.AlignCenter, text)
# 若消息文本存在則在底部居中繪制消息,5秒鐘后清空消息文本并重繪。
if self.message:
#顯示給定坐標處的文本,坐標,對齊方式。文本內(nèi)容
painter.drawText(self.rect(), Qt.AlignBottom | Qt.AlignHCenter,
self.message)
#5秒鐘后觸發(fā)清空信息的函數(shù),并重新繪制事件
QTimer.singleShot(5000, self.clearMessage)
QTimer.singleShot(5000, self.update)
'''清空消息文本的槽函數(shù)'''
def clearMessage(self):
self.message = ""
接下來是調(diào)整窗口大小事件
'''重新實現(xiàn)調(diào)整窗口大小事件'''
def resizeEvent(self, event):
self.text = "調(diào)整窗口大小為: QSize({0}, {1})".format(
event.size().width(), event.size().height())
self.update()

實現(xiàn)鼠標釋放事件,若為雙擊釋放,則不跟隨鼠標移動,若為單擊釋放,則需要跟隨鼠標移動狀態(tài)進行更改,如果開啟跟蹤功能就跟蹤,否則就不跟綜
'''重新實現(xiàn)鼠標釋放事件'''
def mouseReleaseEvent(self, event):
# 若鼠標釋放為雙擊釋放,則不跟蹤鼠標移動
if self.justDoubleClicked:
self.justDoubleClicked = False
# 若鼠標釋放為單擊釋放,則需要改變跟蹤功能的狀態(tài),如果開啟跟蹤功能的話就跟蹤,不開啟跟蹤功能就不跟蹤
else:
# 單擊鼠標
self.setMouseTracking(not self.hasMouseTracking())
if self.hasMouseTracking():
self.text = "開啟鼠標跟蹤功能.\n" + \
"請移動一下鼠標!\n" + \
"單擊鼠標可以關(guān)閉這個功能"
else:
self.text = "關(guān)閉鼠標跟蹤功能.\n" + \
"單擊鼠標可以開啟這個功能"
self.update()



實現(xiàn)鼠標移動事件
'''重新實現(xiàn)鼠標移動事件'''
def mouseMoveEvent(self, event):
#如果沒有鼠標雙擊,執(zhí)行
if not self.justDoubleClicked:
# 窗口坐標轉(zhuǎn)換為屏幕坐標
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()
'''重新實現(xiàn)鼠標雙擊事件'''
def mouseDoubleClickEvent(self, event):
self.justDoubleClicked = True
self.text = "你雙擊了鼠標"
self.update()

實現(xiàn)鍵盤按下事件
'''重新實現(xiàn)鍵盤按下事件'''
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.key = self.key
self.update()
#否則就繼續(xù)監(jiān)視這個事件
else:
QWidget.keyPressEvent(self, event)

重載tab鍵
'''重新實現(xiàn)其他事件,適用于PyQt沒有提供該事件的處理函數(shù)的情況,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)

過濾器的使用
import sys
from PyQt5 import Qt
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class EventFilter(QDialog):
def __init__( self, parent=None ):
super(EventFilter, self).__init__(parent)
self.setWindowTitle('事件過濾器')
#實例化并設(shè)置四個標簽文本
self.label1 = QLabel('請點擊')
self.label2 = QLabel('請點擊')
self.label3 = QLabel('請點擊')
self.labelState = QLabel('test')
#加載三個圖片
self.image1 = QImage('images\cartoon1.ico')
self.image2 = QImage('images\cartoon2.ico')
self.image3 = QImage('images\cartoon3.ico')
self.width = 600
self.height = 300
#設(shè)置初始大小
self.resize(self.width, self.height)
#使用事假過濾器
self.label1.installEventFilter(self)
self.label2.installEventFilter(self)
self.label3.installEventFilter(self)
#設(shè)置窗口布局方式并添加控件
layoyt = QGridLayout(self)
layoyt.addWidget(self.label1, 500, 0)
layoyt.addWidget(self.label2, 500, 1)
layoyt.addWidget(self.label3, 500, 2)
layoyt.addWidget(self.labelState, 600, 1)
def eventFilter( self, watched, event ):
#對事件一的處理過濾機制
if watched == self.label1:
if event.type() == QEvent.MouseButtonPress:
mouseEvent = QMouseEvent(event)
if mouseEvent.buttons() == Qt.LeftButton:
self.labelState.setText('按下鼠標左鍵')
elif mouseEvent.buttons() == Qt.MidButton:
self.labelState.setText('按下鼠標中間鍵')
elif mouseEvent.buttons() == Qt.RightButton:
self.labelState.setText('按下鼠標右鍵')
#轉(zhuǎn)換圖片大小
transform=QTransform()
transform.scale(0.5,0.5)
tmp=self.image1.transformed(transform)
self.label1.setPixmap(QPixmap.fromImage(tmp))
if event.type()==QEvent.MouseButtonRelease:
self.labelState.setText('釋放鼠標按鍵')
self.label1.setPixmap(QPixmap.fromImage(self.image1))
return QDialog.eventFilter(self,watched,event)
if __name__ == '__main__':
app=QApplication(sys.argv)
dialog=EventFilter()
app.installEventFilter(dialog)
dialog.show()
app.exec_()
運行效果如圖

代碼解析
下面的代碼意思是這個過濾器只對label1的事件進行處理,并且只處理它的鼠標按下事件和鼠標釋放事件
def eventFilter( self, watched, event ):
#對事件一的處理過濾機制
if watched == self.label1:
if event.type() == QEvent.MouseButtonPress:
mouseEvent = QMouseEvent(event)
if mouseEvent.buttons() == Qt.LeftButton:
self.labelState.setText('按下鼠標左鍵')
elif mouseEvent.buttons() == Qt.MidButton:
self.labelState.setText('按下鼠標中間鍵')
elif mouseEvent.buttons() == Qt.RightButton:
self.labelState.setText('按下鼠標右鍵')
#轉(zhuǎn)換圖片大小
transform=QTransform()
transform.scale(0.5,0.5)
tmp=self.image1.transformed(transform)
self.label1.setPixmap(QPixmap.fromImage(tmp))
if event.type()==QEvent.MouseButtonRelease:
self.labelState.setText('釋放鼠標按鍵')
self.label1.setPixmap(QPixmap.fromImage(self.image1))
#對于其他的情況會返回系統(tǒng)默認的處理方法
return QDialog.eventFilter(self,watched,event)
一下四行代碼的意思是如果按下這個鼠標鍵,就會對label1裝載的圖片進行縮放一半
#轉(zhuǎn)換圖片大小
transform=QTransform()
transform.scale(0.5,0.5)
tmp=self.image1.transformed(transform)
self.label1.setPixmap(QPixmap.fromImage(tmp))
在QApplication中安裝事件過濾器的使用也非常簡單,只需要修改倆個地方
#使用事件過濾器
# self.label1.installEventFilter(self)
# self.label2.installEventFilter(self)
# self.label3.installEventFilter(self)
if __name__ == '__main__': app=QApplication(sys.argv) dialog=EventFilter() app.installEventFilter(dialog) dialog.show() app.exec_()
運行效果是一樣的
好了,本文主要講解了PyQt5信號與槽事件處理機制詳細介紹與實例解析,更多關(guān)于PyQt5信號與槽的知識請查看下面的相關(guān)鏈接
相關(guān)文章
詳細解讀Python中解析XML數(shù)據(jù)的方法
這篇文章主要介紹了Python中解析XML數(shù)據(jù)的方法,是Python入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-10-10
pycharm配置anaconda環(huán)境時找不到python.exe的兩種解決辦法
如果你在Anaconda中創(chuàng)建了虛擬環(huán)境,但是無法找到python.exe,可能是因為虛擬環(huán)境的Python路徑?jīng)]有添加到系統(tǒng)環(huán)境變量中,這篇文章主要給大家介紹了關(guān)于pycharm配置anaconda環(huán)境時找不到python.exe的兩種解決辦法,需要的朋友可以參考下2024-07-07
Python爬蟲基礎(chǔ)之爬蟲的分類知識總結(jié)
來給大家講python爬蟲的基礎(chǔ)啦,首先我們從爬蟲的分類開始講起,下文有非常詳細的知識總結(jié),對正在學(xué)習(xí)python的小伙伴們很有幫助,需要的朋友可以參考下2021-05-05
淺談Python實現(xiàn)opencv之圖片色素的數(shù)值運算和邏輯運算
今天帶大家來學(xué)習(xí)的是關(guān)于Python的相關(guān)知識,文章圍繞著圖片色素的數(shù)值運算和邏輯運算展開,文中有非常詳細的的介紹及代碼示例,需要的朋友可以參考下2021-06-06
Python數(shù)據(jù)持久化shelve模塊用法分析
這篇文章主要介紹了Python數(shù)據(jù)持久化shelve模塊用法,結(jié)合實例形式較為詳細的總結(jié)分析了shelve模塊的功能、原理及簡單使用方法,需要的朋友可以參考下2018-06-06

