PyQt GUI--信號與槽

目錄

前言

PyQt中的信號和槽,就是一個觸發和執行的關係。css

系統軟件

  • 系統
    • Win 10
  • 軟件
    • Python 3.4.3
    • IPython 4.0.0
    • PyCharm 5
    • PyQt 4

GUI的主循環

在理解信號和槽以前,首先先了解GUI的實現過程。
GUI程具備事件驅動的特性,當一個GUI程序完成了初始化啓動後,就會進入一個」服務器式」的無限循環中。在PyQt中使用QtGui.QApplication.exec_()做爲進入主循環的標誌。進入了主循環後,在這個循環週期中GUI程序會等待事件、處理事件、而後返回等待下一個事件。這一般是最後一行代碼,一旦進入了主循環,GUI程序今後得到了控制權。自此以後,GUI程序的全部動做都交由callback來處理。而Qt中的信號和槽就是GUI程序中事件觸發和callback之間的通訊機制。極大的簡化了指針調用在GUI程序中的複雜實現。python

信號與槽

信號(Signal)和槽(Slot)是一種高級接口,應用於QObject間的通訊。在GUI開發中,窗口控件會有一個回調函數(callback)用於響應因其自身的狀態改變而觸發的動做。i.e.:當QObject的狀態被改變,信號就由該QObject發射(emit)出去,並且爲了作到信息封裝,QObject並不關注發送以後的事情。 槽用於接收信號並執行動做,是一個對象的成員方法,可以直接被調用。一樣的,槽也並不關注有哪一個信號與本身鏈接。信號和槽可以進行任意數量和任意類型的參數傳遞,但信號和槽的參數個數與類型必須一致,而且槽的參數類型不能爲缺省參數。
信號Signal
信號均可以被QObject包含,當QObject的狀態發生改變時(e.g. Button被Clicked、QWidget被Clicked),QObject指定的信號就會被髮射。並且信號自身並不知道那個槽會接收本身,QObject只管發射信號。當一個信號被髮射時,與其鏈接的槽將被馬上執行,就象一個正常的函數調用同樣。
只有當與信號鏈接的全部槽都返回了之後,信號發射函數(emit)纔會被返回。 若是存在多個槽與某個信號鏈接,當這個信號被髮射時,這些槽將會無序的逐一執行,它們執行的順序將會是隨機的、不肯定的,不能人爲設定。在PyQt的窗體控件類中已經有不少內置的信號,固然你也能夠本身定義信號。
槽:
在PyQt中的槽就是一個通過裝飾器@QtCore.pyqtSlot()處理過的成員方法。槽惟一的特殊性就是能夠與多個信號鏈接。當與其鏈接的信號被髮射出來時,這個槽就會接受信號並被調用。
信號與槽鏈接的方式:
1)多個信號能夠與單個的槽鏈接
2)單個信號也能夠與多個的槽進行鏈接
3)一個信號能夠與另一個信號鏈接,這時不管第一個信號何時發射,系統都將馬上再發射與之關聯的第二個信號。
鏈接的狀態:
1)能夠能會直接鏈接(同步,一對一)或排隊等待鏈接(異步,多對一)
2)鏈接可能會跨線程
3)信號可能會斷開
總結:
1. 類型安全:只有參數匹配的信號與槽才能夠鏈接成功(信號的參數能夠更多,槽會忽略多餘的參數)。
2. 線程安全:經過藉助QT自已的事件機制,信號槽支持跨線程而且能夠保證線程安全。
3. 鬆耦合:信號不關心有哪些或者多少個對象與之鏈接;槽不關心本身鏈接了哪些對象的哪些信號。這些都不會影響什麼時候發出信號或者信號如何處理。
4. 信號與槽是多對多的關係:一個信號能夠鏈接多個槽,一個槽也能夠用來接收多個信號。安全

Example:服務器

提示窗口QtGui.QMessageBox()類的原型:
QMessageBox.information(QWidget, str, str, int, int button1=0, int button2=0) -> int
from PyQt4 import QtGui, QtCore
app = QtGui.QApplication([])
w = QtGui.QWidget()
def showMsg():
    QtGui.QMessageBox.information(w, "Tip", "ok")     #QtGui.QMessageBox.information(QWidget,Title,information)彈出提示窗口,可以設定提示信息和按鈕
btn = QtGui.QPushButton("Click", w)   
w.connect(btn, QtCore.SIGNAL("clicked()"), showMsg)
w.show()
app.exec_()
#Button和MessageBox都是是以w做爲父窗口。而且Button和MessageBox作了信號與槽的鏈接,當call Button.clicked()信號時,發送信號。並由做爲槽的MessageBox接受信號,再作出動做。

這個例子還能夠有這一種寫法(調用鏈接方法的另外一個方式):markdown

from PyQt4 import QtGui, QtCore

app = QtGui.QApplication([])

w = QtGui.QWidget()

def showMsg():
    QtGui.QMessageBox.information(w, u"信息", u"ok")

btn = QtGui.QPushButton(u"點我", w)

btn.clicked.connect(showMsg)   # 也可使用這種方法來鏈接信號與槽,而且邏輯更加的清晰。w.connect(btn, QtCore.SIGNAL("clicked()"), showMsg)。這是相似於 JQuery 響應事件的方式

app.exec_()

在上面的兩個例子中,鏈接信號clicked()與callback showMsg。這是使用成員方法做爲槽的例子,在實際的項目中這並非一個理想的寫法。
這裏寫圖片描述app

信號的應用

使用控件類的內建信號

PushButton:
class QPushButton(QAbstractButton)
| QPushButton(QWidget parent=None)
| QPushButton(str, QWidget parent=None)
| QPushButton(QIcon, str, QWidget parent=None)異步

from PyQt4.QtGui import *  
from PyQt4.QtCore import *  
import sys  

app=QApplication(sys.argv)                       
b=QPushButton("Hello Kitty!")       #QPushButton(str, QWidget parent=None)  str參數設定ButtonText                    
b.show()                         
app.connect(b,SIGNAL("clicked()"),app,SLOT("quit()"))   #return bool;使用內建的clicked()信號對象
app.exec_()  #調用QApplication的exec_()方法,程序進入消息循環,等待可能的輸入並進行響應。Qt完成事件處理及顯示的工做,並在應用程序退出時返回exec_()的值。
###解析:
#class QApplication(QGuiApplication)建立app對象,全部Qt圖形化應用程序都必須包含QApplication()類,它包含了Qt GUI的各類資源,基本設置,控制流以及事件處理等。
#採用sys.argv做爲QApplication()的實參,是爲了便於程序處理命令行參數。
#sys.argv[]用來獲取命令行參數,sys.argv[0]表示程序文件自己(路徑)
#建立了一個QPushButton對象,並設置它的顯示文本爲「Hello Kitty!」,因爲此處並無指定按鈕的父窗體,所以本身做爲頂層窗口對象。
#show(...) --> QWidget.show() 調用show()方法,顯示此主窗口
#connect方法是Qt最重要的特徵,即信號與槽的機制。當按鈕被按下則觸發clicked信號,與之相連的槽(PyQt4.QtGui.QApplication的實例化對象app的quit()成員方法)會接收clisked()信號,執行退出應用程序的操做
#部件是分層的,每個小部件均可以依賴於父層(主窗口)之上,也能夠本身做爲主窗口
#當本身做爲主窗口的時候能夠call show()方法,本質也是call QWidget.show()

自定義信號

信號類
class pyqtSignal(builtins.object)
| pyqtSignal(*types, name=str) -> signal
不定長形參*types指定信號須要傳遞的參數類型,PyQt中的Signal能夠接受任意Python數據類型。模塊化

class MyWidget(QWidget):  
    Signal_NoParameters = PyQt4.QtCore.pyqtSignal()                                  # 無參數信號 
    Signal_OneParameter = PyQt4.QtCore.pyqtSignal(int)                               # 一個參數(整數)的信號 
    Signal_OneParameter_Overload = PyQt4.QtCore.pyqtSignal([int],[str])              # 一個參數(整數或者字符串)重載版本的信號 
    Signal_TwoParameters = PyQt4.QtCore.pyqtSignal(int,str)                          # 二個參數(整數,字符串)的信號 
    Signal_TwoParameters_Overload = PyQt4.QtCore.pyqtSignal([int,int],[int,str])     # 二個參數([整數,整數]或者[整數,字符串])重載版本的信號 

Example1函數

from PyQt4 import QtGui, QtCore

class MyButton(QtGui.QPushButton):         #將自定義信號封裝(綁定)到Class MyButton
    myclicked = QtCore.pyqtSignal()        #pyqtSignal(*types, name=str) -> signal建立自定義信號對象myclicked,具備成員方法emit()

    def __init__(self, *args, **kwargs):         #重寫基類構造器,若是不重寫將會自動繼承QPushButton.__init__(self)
        QtGui.QPushButton.__init__(self, *args, **kwargs)     #在重寫了基類構造器以後,須要顯式寫出,基類構造器纔會被調用 
        self.connect(self, QtCore.SIGNAL("clicked()"), self.myclicked.emit)  #至關於內建信號與自定義信號的鏈接,在每次call clicked()後就會調用自定義信號的發射方法emit來發送信號。(self.myclicked.emit) 
app = QtGui.QApplication([])
w = QtGui.QWidget()

def showMsg():
    QtGui.QMessageBox.information(w, u"信息", u"ok")        #彈出信息提示窗口,使用callback做爲槽。以QWidget做爲父層控件

btn = MyButton(u"點我", w)      #建立Button對象btn,並將clicked()信號映射成爲myclicked()信號(信號之間的鏈接)綁定在btn對象中。
w.connect(btn, QtCore.SIGNAL("myclicked()"), showMsg)     #將自定義信號與槽鏈接,QObject btn含有自定義信號myclicked。在觸發clicked()事件後,btn發射myclicked()信號,callback showMsg執行動做。
w.show()
app.exec_()

爲了發射自定義的信號, 須要對QPushButton進行封裝, 實例化建立button對象時自動綁定myclicked()信號 。封裝QPushButton讓他在收到點擊信號的同時發送myclicked()信號。在實際項目中,對信號進行封裝是一件頗有必要的事情,可以讓整個項目更加的模塊化和易於維護。
Example2:另一種寫法ui

from PyQt4 import QtGui, QtCore

class MyButton(QtGui.QPushButton):
    def __init__(self, *args, **kwargs):
        QtGui.QPushButton.__init__(self, *args, **kwargs)
        self.connect(self, QtCore.SIGNAL("clicked()"), self.emitClicked)  #self.connect(self, QtCore.SIGNAL("clicked()"), self.emit(QtCore.SIGNAL("myclicked()"))) 

    def emitClicked(self):
        self.emit(QtCore.SIGNAL("myclicked()"))   #QtCore.SIGNAL(str) -> str 發送一個str做爲信號


app = QtGui.QApplication([])
w = QtGui.QWidget()

def showMsg():
    QtGui.QMessageBox.information(w, u"信息", u"ok")


btn = MyButton(u"點我", w)
w.connect(btn, QtCore.SIGNAL("myclicked()"), showMsg)
w.show()

app.exec_()

注意:上述兩種寫法效果是同樣的,可是實現的本質卻不同。
Example1中定義了一個新的signalObject,而且經過call signalObject.emit()來發送信號對象自身。
Example2中沒有定義新的signalObject,而是鏈接了信號clicked()和」槽」(使用成員方法代替槽)emitClicked(),最後經過」槽」將信號(str)發射出去。
這裏寫圖片描述

帶參數的信號

信號能夠傳遞參數給槽,但須要注意的是隻有參數匹配的信號與槽才能夠鏈接成功(信號的參數能夠更多,槽會忽略多餘的參數)。
控件類原型:

In [14]: help(QtGui.QPushButton.__init__)
Help on wrapper_descriptor:

__init__(self, /, *args, **kwargs)
    Initialize self.  See help(type(self)) for accurate signature.

Example1:

from PyQt4 import QtGui, QtCore

class MyButton(QtGui.QPushButton):
    myclicked = QtCore.pyqtSignal(int)               #自定義了一個帶參數的信號對象myclicked,e.g. Signal_OneParameter = PyQt4.QtCore.pyqtSignal(int)

    def __init__(self, _id, *args, **kwargs):         #構造器,決定了實例化對象時的須要傳遞的實參數目。*args、**kwargs爲不定長形參,以序列、字典的形式吸取實參冗餘。_id爲私有屬性
        QtGui.QPushButton.__init__(self, *args, **kwargs)
        self._id = _id
        self.connect(self, QtCore.SIGNAL("clicked()"), self.emitMyclicked)   #self.connect(self, QtCore.SIGNAL("clicked()"), self.myclicked.emit(self._id))

    def emitMyclicked(self):
        self.myclicked.emit(self._id)         

app = QtGui.QApplication([])
w = QtGui.QWidget()
w.resize(100, 100)

def showMsg(_id):
    QtGui.QMessageBox.information(w, u"信息", u"查看 %d" % _id)     #接收到來自信號的參數


btn = MyButton(1, u"查看1", w)                  
w.connect(btn, QtCore.SIGNAL("myclicked(int)"), showMsg)        #聲明信號是帶有int類型參數的,並將信號和參數都發送給槽。信號和槽的參數類型必須一致,且數量最好相等。
btn2 = MyButton(2, u"查看2", w)
btn2.move(0, 30)
w.connect(btn2, QtCore.SIGNAL("myclicked(int)"), showMsg)

w.show()
app.exec_()

另外一種寫法

from PyQt4 import QtGui, QtCore

class MyButton(QtGui.QPushButton):
    def __init__(self, _id, *args, **kwargs):
        self._id = _id
        QtGui.QPushButton.__init__(self, *args, **kwargs)

        self.connect(self, QtCore.SIGNAL("clicked()"), self.emitClicked)

    def emitClicked(self):
        self.emit(QtCore.SIGNAL("myclicked(int)"), self._id)     #發送信號(str)而且指定須要傳遞的參數的值self._id


app = QtGui.QApplication([])

w = QtGui.QWidget()
w.resize(100, 100)

def showMsg(_id):
    QtGui.QMessageBox.information(w, u"信息", u"查看 %d" % _id)


btn = MyButton(1, u"查看1", w)
w.connect(btn, QtCore.SIGNAL("myclicked(int)"), showMsg)

btn2 = MyButton(2, u"查看2", w)
btn2.move(0, 30)
w.connect(btn2, QtCore.SIGNAL("myclicked(int)"), showMsg)
w.show()

app.exec_()

這裏寫圖片描述

Example2

# coding=gbk
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class MyWidget(QWidget):                              #封裝QWidget
    OnClicked = pyqtSignal([int,int],[int,str])       #自定義帶有兩個參數的Signal,能夠傳遞任一組合類型實參
    def __init__(self, parent=None):                  #構造方法
        super(MyWidget,self).__init__(parent)            #調用MyWidget類的基類的構造方法,super()用於調用父類函數的成員方法

    def mousePressEvent(self, event):            #重寫QtGui.QWidget.mousePressEvent(QMouseEvent)鼠標點擊方法;行爲:根據點擊鼠標主窗口的方式來發射帶有不一樣參數的自定義信號發射自定義信號
        if event.button() == Qt.LeftButton:           #QtCore.Qt.LeftButton ;QMouseEvent.button() -> Qt.MouseButton
            self.OnClicked.emit(event.x(),event.y())      #QMouseEvent.x() -> int;獲取點擊的座標;發送兩個Int類型參數
            event.accept()
        elif event.button() == Qt.RightButton:
            self.OnClicked[int,str].emit(event.x(),str(event.y()))    #發送一個Int類型參數,一個String類型參數
            event.accept()
        else:
            super(MyWidget,self).mousePressEvent(self, event)         #調用基類的mousePressEvent(self, event)方法

def OnValueChanged_int(x,y):          #充當槽的方法,接收兩個Int類型的實參
    print("左鍵(%d,%d)" % (x,y))

def OnValueChanged_string(szX,szY):   #充當槽的方法,接收一個Int類型一個String類型實參
    print('右鍵(' + str(szX) + ',' + szY + ')')

app = QApplication(sys.argv)
widget = MyWidget()
widget.show()
widget.OnClicked.connect(OnValueChanged_int,Qt.QueuedConnection)  #將自定義的Signal OnClicked和槽OnValueChanged_int鏈接
widget.OnClicked[int,str].connect(OnValueChanged_string,Qt.QueuedConnection)
sys.exit(app.exec_())

槽的應用

建立槽

QtCore.pyqtSlot()函數返回一個裝飾器用於裝飾 QObject 的方法, 使之成爲一個槽
定義槽

class MyWidget(QWidget):  
    ...  
    @PyQt4.QtCore.pyqtSlot() 
    def setValue_NoParameters(self):   
        '''無參數槽方法'''  
        pass  
    @PyQt4.QtCore.pyqtSlot(int) 
    def setValue_OneParameter(self,nIndex):   
       '''一個參數(整數)槽方法'''  
        pass  
    @PyQt4.QtCore.pyqtSlot(str) 
    def setValue_OneParameter_String(self,szIndex):   
       '''一個參數(字符串)的槽方法'''  
        pass  
    @PyQt4.QtCore.pyqtSlot(int,int) 
    def setValue_TwoParameters(self,x,y):   
       '''二個參數(整數,整數)槽方法'''  
        pass  
    @PyQt4.QtCore.pyqtSlot(int,str) 
    def setValue_TwoParameters_String(self,x,szY):   
       '''二個參數(整數,字符串)槽方法'''  
        pass

Example:

from PyQt4 import QtGui, QtCore

class MainWidget(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        btn = QtGui.QPushButton(u"點我", self)             #建立一個Button對象
        self.connect(btn, QtCore.SIGNAL("clicked()"),self,QtCore.SLOT("onClicked()"))  #鏈接信號QtCore.SIGNAL("clicked()"),和槽QtCore.SLOT("onClicked()")

    @QtCore.pyqtSlot() #裝飾器;經過@PyQt4.QtCore.pyqtSlot裝飾方法定義槽函數
    def onClicked(self):  #經過裝飾器的處理,onClicked(self)成員方法就成爲了一個槽,能夠接收多個信號的鏈接
        QtGui.QMessageBox.information(self, u"信息", u"由槽彈出")


app = QtGui.QApplication([])
m = MainWidget()
m.show()
app.exec_()
#槽,能夠簡單理解爲一個通過裝飾器處理的方法。

上面這個例子跟以前的例子的表現結果沒有任何區別,可是這個例子中的信號鏈接了真正的槽函數,並且這個槽能夠被多個信號鏈接。

信號和槽的鏈接

鏈接類的原型

connect(...) 
    QObject.connect(QObject, SIGNAL(), QObject, SLOT(), Qt.ConnectionType=Qt.AutoConnection) -> bool
    QObject.connect(QObject, SIGNAL(), callable, Qt.ConnectionType=Qt.AutoConnection) -> bool
    QObject.connect(QObject, SIGNAL(), SLOT(), Qt.ConnectionType=Qt.AutoConnection) -> bool

鏈接信號和槽:
調用的方式:Signal.connect/QApplication.connect/QObject.connect/QObject.Signal.connect

app = QApplication(sys.argv)   
widget = MyWidget()   
widget.show()   
widget.Signal_NoParameters.connect(self.setValue_NoParameters,Qt.QueuedConnection)                                          # 鏈接無參數信號 
widget.Signal_OneParameter.connect(self.setValue_OneParameter,Qt.QueuedConnection)                                          # 鏈接一個參數(整數)信號 
widget.Signal_OneParameter_Overload[int].connect(self.setValue_OneParameter,Qt.QueuedConnection)                            # 鏈接一個參數(整數)重載版本信號 
widget.Signal_OneParameter_Overload[str].connect(self.setValue_OneParameter_String,Qt.QueuedConnection)                     # 鏈接一個參數(整數)重載版本信號 
widget.Signal_TwoParameters.connect(self.setValue_TwoParameters,Qt.QueuedConnection)                                        # 鏈接二個參數(整數)信號 
widget.Signal_TwoParameters_Overload[int,int].connect(self.setValue_TwoParameters,Qt.QueuedConnection)                      # 鏈接二個參數(整數,整數)重載版本信號 
widget.Signal_TwoParameters_Overload[int,str].connect(self.setValue_TwoParameters_String,Qt.QueuedConnection)               # 鏈接二個參數(整數,字符串)重載版本信號 

最後

在這篇博文中,主要記錄了在PyQt中的信號和槽的定義和基本概念。還有在網上找的幾個例子來加深對概念的理解,下一次咱們結合QT Designer來實現一些窗體的小部件。

:- )

相關文章
相關標籤/搜索