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来实现一些窗体的小部件。

:- )

相关文章
相关标签/搜索