<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Python Qt GUI設(shè)計:5種事件處理機(jī)制(提升篇—3)

          共 12065字,需瀏覽 25分鐘

           ·

          2021-12-16 22:02

          點(diǎn)擊上方藍(lán)色字體,關(guān)注我們


          之前在Python Qt GUI設(shè)計:QTimer計時器類、QThread多線程類和事件處理類(基礎(chǔ)篇—8)中,我們已經(jīng)簡單講到,PyQt為事件處理提供了兩種機(jī)制:高級的信號與槽機(jī)制以及低級的事件處理程序,本篇博文將系統(tǒng)講解Qt的事件處理機(jī)類和制。


          事件處理機(jī)制本身很復(fù)雜,是PyQt底層的知識點(diǎn),當(dāng)采用信號與槽機(jī)制處理不了時,才會考慮使用事件處理機(jī)制。


          信號與槽可以說是對事件處理機(jī)制的高級封裝,如果說事件是用來創(chuàng)建窗口控件的,那么信號與槽就是用來對這個窗口控件進(jìn)行使用的。比如一個按鈕,當(dāng)我們使用這個按鈕時,只關(guān)心clicked信號,至于這個按鈕如何接收并處理鼠標(biāo)點(diǎn)擊事件,然后再發(fā)射這信號,則不用關(guān)心。但是如果要重載一個按鈕,這時就要關(guān)心這個問題了。比如可以改變它的行為:在鼠標(biāo)按鍵按下時觸發(fā)clicked信號,而不是在釋放時。



          1


          常見事件類型



          Qt事件的類型有很多,常見的Qt事件如下所示:

          • 鍵盤事件:按鍵按下和松開。

          • 鼠標(biāo)事件:鼠標(biāo)指針移動、鼠標(biāo)按鍵按下和松開。

          • 拖放事件:用鼠標(biāo)進(jìn)行拖放。

          • 滾輪事件:鼠標(biāo)滾輪滾動。

          • 繪屏事件:重繪屏幕的某些部分。

          • 定時事件:定時器到時。

          • 焦點(diǎn)事件:鍵盤焦點(diǎn)移動。

          • 進(jìn)入和離開事件:鼠標(biāo)指針移入Widget內(nèi),或者移出。

          • 移動事件::Widget的位置改變。

          • 大小改變事件:Widget的大小改變。

          • 顯示和隱藏事件:Widget顯示和隱藏。

          • 窗口事件:窗口是否為當(dāng)前窗口。


          還有一些常見的Qt事件,比如Socket事件、剪貼板事件、字體改變事件、布局改變事件等。


          具體事件類型和說明可參見說明文檔:




          2


          事件處理方法



          PyQt提供了如下5種事件處理和過濾方法(由弱到強(qiáng)),其中只有前兩種方法使用最頻繁。


          2.1、重新實現(xiàn)事件函數(shù)


          比如mousePressEvent()、keyPressEvent()、paintEvent()。這是最常規(guī)的事件處理方法。


          通過示例了解重新實現(xiàn)事件函數(shù)的使用方法,效果如下所示:



          這個示例中包含了多種事件類型,所以比較復(fù)雜。


          首先是類的建立,建立text和message兩個變量,使用paintEvent函數(shù)把它們輸出到窗口中。


          update函數(shù)的作用是更新窗口,由于在窗口更新過程中會觸發(fā)一次 paintEvent函數(shù)(paintEvent是窗口基類QWidget的內(nèi)部函數(shù)),因此在本例中update函數(shù)的作用等同于paintEvent函數(shù)。


          然后是重新實現(xiàn)窗口關(guān)閉事件與上下文菜單事件,對于上下文菜單事件,主要影響message變量的結(jié)果,paintEvent負(fù)責(zé)把這個變量在窗口底部輸出。


          繪制事件是代碼的核心事件,它的主要作用是時刻跟蹤text與message這兩個變量的信息,并把 text的內(nèi)容繪制到窗口的中部,把message的內(nèi)容繪制到窗口的底部(保持5秒后就會被清空)。


          以及最后一些鼠標(biāo)、鍵盤的點(diǎn)擊操作等。


          實現(xiàn)代碼如下所示:


          import sysfrom 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) self.justDoubleClicked = False self.key = "" self.text = "" self.message = "" self.resize(400, 300) self.move(100, 100) self.setWindowTitle("Events") QTimer.singleShot(0, self.giveHelp) # 避免窗口大小重繪事件的影響,可以把參數(shù)0改變成3000(3秒),然后在運(yùn)行,就可以明白這行代碼的意思。
          def giveHelp(self): self.text = "請點(diǎn)擊這里觸發(fā)追蹤鼠標(biāo)功能" self.update() # 重繪事件,也就是觸發(fā)paintEvent函數(shù)。
          '''重新實現(xiàn)關(guān)閉事件''' def closeEvent(self, event): print("Closed")
          '''重新實現(xiàn)上下文菜單事件''' def contextMenuEvent(self, event): menu = QMenu(self) oneAction = menu.addAction("&One") twoAction = menu.addAction("&Two") oneAction.triggered.connect(self.one) twoAction.triggered.connect(self.two) if not self.message: menu.addSeparator() threeAction = menu.addAction("Thre&e") threeAction.triggered.connect(self.three) 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] if self.key: # 若觸發(fā)了鍵盤按鈕,則在文本信息中記錄這個按鈕信息。 text += "\n\n你按下了: {0}".format(self.key) painter = QPainter(self) painter.setRenderHint(QPainter.TextAntialiasing) painter.drawText(self.rect(), Qt.AlignCenter, text) # 繪制信息文本的內(nèi)容 if self.message: # 若消息文本存在則在底部居中繪制消息,5秒鐘后清空消息文本并重繪。 painter.drawText(self.rect(), Qt.AlignBottom | Qt.AlignHCenter, self.message) 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)鼠標(biāo)釋放事件''' def mouseReleaseEvent(self, event): # 若鼠標(biāo)釋放為雙擊釋放,則不跟蹤鼠標(biāo)移動 # 若鼠標(biāo)釋放為單擊釋放,則需要改變跟蹤功能的狀態(tài),如果開啟跟蹤功能的話就跟蹤,不開啟跟蹤功能就不跟蹤 if self.justDoubleClicked: self.justDoubleClicked = False else: self.setMouseTracking(not self.hasMouseTracking()) # 單擊鼠標(biāo) if self.hasMouseTracking(): self.text = "開啟鼠標(biāo)跟蹤功能.\n" + \ "請移動一下鼠標(biāo)!\n" + \ "單擊鼠標(biāo)可以關(guān)閉這個功能" else: self.text = "關(guān)閉鼠標(biāo)跟蹤功能.\n" + \ "單擊鼠標(biāo)可以開啟這個功能" self.update()
          '''重新實現(xiàn)鼠標(biāo)移動事件''' def mouseMoveEvent(self, event): if not self.justDoubleClicked: globalPos = self.mapToGlobal(event.pos()) # 窗口坐標(biāo)轉(zhuǎn)換為屏幕坐標(biāo) self.text = """鼠標(biāo)位置: 窗口坐標(biāo)為:QPoint({0}, {1}) 屏幕坐標(biāo)為:QPoint({2}, {3}) """.format(event.pos().x(), event.pos().y(), globalPos.x(), globalPos.y()) self.update()
          '''重新實現(xiàn)鼠標(biāo)雙擊事件''' def mouseDoubleClickEvent(self, event): self.justDoubleClicked = True self.text = "你雙擊了鼠標(biāo)" 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() if self.key: self.key = self.key self.update() else: QWidget.keyPressEvent(self, event)

          if __name__ == "__main__": app = QApplication(sys.argv) form = Widget() form.show() app.exec_()


          2.2、重新實現(xiàn)QObject.event()


          一般用在PyQt沒有提供該事件的處理函數(shù)的情況下,即增加新事件時。


          對于窗口所有的事件都會傳遞給event函數(shù),event函數(shù)會根據(jù)事件的類型,把事件分配給不同的函數(shù)進(jìn)行處理。例如,對于繪圖事件,event會交給paintEvent函數(shù)處理;對于鼠標(biāo)移動事件,event會交給mouseMoveEvent函數(shù)處理;對于鍵盤按下事件,event會交給keyPressEvent函數(shù)處理。


          有一種特殊情況是對Tab鍵的觸發(fā)行為,event函數(shù)對Tab鍵的處理機(jī)制是把焦點(diǎn)從當(dāng)前窗口控件的位置切換到Tab鍵次序中下一個窗口控件的位置,并返回True,而不是交給keyPressEvent函數(shù)處理。


          因此這里需要在event函數(shù)中對按下Tab鍵的處理邏輯重新改寫,使它與鍵盤上普通的鍵沒什么不同。


          2.1、重新實現(xiàn)事件函數(shù)例子中補(bǔ)充以下代碼,實現(xiàn)重新定義:


          '''重新實現(xiàn)其他事件,適用于PyQt沒有提供該事件的處理函數(shù)的情況,Tab鍵由于涉及焦點(diǎn)切換,不會傳遞給keyPressEvent,因此,需要在這里重新定義。'''


          def event(self, event):        if (event.type() == QEvent.KeyPress and                    event.key() == Qt.Key_Tab):            self.key = "在event()中捕獲Tab鍵"            self.update()            return True


          效果如下所示:



          2.3、安裝事件過濾器


          如果對QObject調(diào)用installEventFilter,則相當(dāng)于為這個QObject安裝了一個事件過濾器,對于QObject的全部事件來說,它們都會先傳遞到事件過濾函數(shù)eventFilter中,在這個函數(shù)中我們可以拋棄或者修改這些事件,比如可以對自己感興趣的事件使用自定義的事件處理機(jī)制,對其他事件使用默認(rèn)的事件處理機(jī)制。


          由于這種方法會對調(diào)用installEventFilter的所有QObject的事件進(jìn)行過濾,因此如果要過濾的事件比較多,則會降低程序的性能。


          通過示例,了解事件過濾器的使用方法,效果如下所示:



          對于使用事件過濾器,關(guān)鍵是要做好兩步。


          對要過濾的控件設(shè)置installEventFilter,這些控件的所有事件都會被eventFilter函數(shù)接收并處理。


          示例中,這個過濾器只對label1的事件進(jìn)行處理,并且只處理它的鼠標(biāo)按下事件(MouseButtonPress)和鼠標(biāo)釋放事件(MouseButtonRelease) 。


          如果按下鼠標(biāo)鍵,就會對label1裝載的圖片進(jìn)行縮放(長和寬各縮放一半)。

          實現(xiàn)代碼如下所示:


          -*- coding: utf-8 -*-from PyQt5.QtGui import *from PyQt5.QtCore import *from PyQt5.QtWidgets import *import sysclass EventFilter(QDialog):    def __init__(self, parent=None):        super(EventFilter, self).__init__(parent)        self.setWindowTitle("事件過濾器")        self.label1 = QLabel("請點(diǎn)擊")        self.label2 = QLabel("請點(diǎn)擊")        self.label3 = QLabel("請點(diǎn)擊")        self.LabelState = QLabel("test")        self.image1 = QImage("images/cartoon1.ico")        self.image2 = QImage("images/cartoon1.ico")        self.image3 = QImage("images/cartoon1.ico")        self.width = 600        self.height = 300        self.resize(self.width, self.height)        self.label1.installEventFilter(self)        self.label2.installEventFilter(self)        self.label3.installEventFilter(self)        mainLayout = QGridLayout(self)        mainLayout.addWidget(self.label1, 500, 0)        mainLayout.addWidget(self.label2, 500, 1)        mainLayout.addWidget(self.label3, 500, 2)        mainLayout.addWidget(self.LabelState, 600, 1)        self.setLayout(mainLayout)    def eventFilter(self, watched, event):        if watched == self.label1: # 只對label1的點(diǎn)擊事件進(jìn)行過濾,重寫其行為,其他的事件會被忽略            if event.type() == QEvent.MouseButtonPress: # 這里對鼠標(biāo)按下事件進(jìn)行過濾,重寫其行為                mouseEvent = QMouseEvent(event)                if mouseEvent.buttons() == Qt.LeftButton:                    self.LabelState.setText("按下鼠標(biāo)左鍵")                elif mouseEvent.buttons() == Qt.MidButton:                    self.LabelState.setText("按下鼠標(biāo)中間鍵")                elif mouseEvent.buttons() == Qt.RightButton:                    self.LabelState.setText("按下鼠標(biāo)右鍵")                '''轉(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: # 這里對鼠標(biāo)釋放事件進(jìn)行過濾,重寫其行為                self.LabelState.setText("釋放鼠標(biāo)按鈕")                self.label1.setPixmap(QPixmap.fromImage(self.image1))        return QDialog.eventFilter(self, watched, event) # 其他情況會返回系統(tǒng)默認(rèn)的事件處理方法。if __name__ == '__main__':    app = QApplication(sys.argv)    dialog = EventFilter()    dialog.show()    sys.exit(app.exec_())


          2.4、在QApplication中安裝事件過濾器


          這種方法比2.3、安裝事件過濾器更強(qiáng)大,QApplication的事件過濾器將捕獲所有QObject的所有事件,而且第一個獲得該事件。也就是說,在將事件發(fā)送給其他任何一個事件過濾器之前(就是在第三種方法之前),都會先發(fā)送給QApplication的事件過濾器。


          2.3、安裝事件過濾器示例基礎(chǔ)上修改,屏蔽三個label標(biāo)簽控件的installEventFilter代碼,這種事件處理方法確實過濾了所有事件,而不像第三種方法那樣只過濾三個標(biāo)簽控件的事件。效果如下所示:



          實現(xiàn)代碼如下所示:


          -*- coding: utf-8 -*-from PyQt5.QtGui import *from PyQt5.QtCore import *from PyQt5.QtWidgets import *import sysclass EventFilter(QDialog):    def __init__(self, parent=None):        super(EventFilter, self).__init__(parent)        self.setWindowTitle("事件過濾器")        self.label1 = QLabel("請點(diǎn)擊")        self.label2 = QLabel("請點(diǎn)擊")        self.label3 = QLabel("請點(diǎn)擊")        self.LabelState = QLabel("test")        self.image1 = QImage("images/cartoon1.ico")        self.image2 = QImage("images/cartoon1.ico")        self.image3 = QImage("images/cartoon1.ico")        self.width = 600        self.height = 300        self.resize(self.width, self.height)        # self.label1.installEventFilter(self)        # self.label2.installEventFilter(self)        # self.label3.installEventFilter(self)        mainLayout = QGridLayout(self)        mainLayout.addWidget(self.label1, 500, 0)        mainLayout.addWidget(self.label2, 500, 1)        mainLayout.addWidget(self.label3, 500, 2)        mainLayout.addWidget(self.LabelState, 600, 1)        self.setLayout(mainLayout)    def eventFilter(self, watched, event):        print(type(watched))        if watched == self.label1: # 只對label1的點(diǎn)擊事件進(jìn)行過濾,重寫其行為,其他的事件會被忽略            if event.type() == QEvent.MouseButtonPress: # 這里對鼠標(biāo)按下事件進(jìn)行過濾,重寫其行為                mouseEvent = QMouseEvent(event)                if mouseEvent.buttons() == Qt.LeftButton:                    self.LabelState.setText("按下鼠標(biāo)左鍵")                elif mouseEvent.buttons() == Qt.MidButton:                    self.LabelState.setText("按下鼠標(biāo)中間鍵")                elif mouseEvent.buttons() == Qt.RightButton:                    self.LabelState.setText("按下鼠標(biāo)右鍵")                '''轉(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: # 這里對鼠標(biāo)釋放事件進(jìn)行過濾,重寫其行為                self.LabelState.setText("釋放鼠標(biāo)按鈕")                self.label1.setPixmap(QPixmap.fromImage(self.image1))        return QDialog.eventFilter(self, watched, event) # 其他情況會返回系統(tǒng)默認(rèn)的事件處理方法。if __name__ == '__main__':    app = QApplication(sys.argv)    dialog = EventFilter()    app.installEventFilter(dialog)    dialog.show()    sys.exit(app.exec_())


          2.5、重新實現(xiàn)QApplication的notify()方法


          PyQt使用notify()來分發(fā)事件,要想在任何事件處理器之前捕獲事件,唯一的方法就是重新實現(xiàn)QApplication的notify(),在實踐中,在調(diào)試時才會使用這種方法,實際中基本用不多,這里不再贅述了。


          往期推薦



          點(diǎn)擊閱讀原文,更精彩~
          瀏覽 48
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品激情五月综合 | 人妻在线视频播放 | 91影院体验区 | 三级草逼 | 国产午夜精品电影 |