PyQt中处理信号和槽时遇到的问题和解决方法
昨天在群里一个朋友提出了一个问题,要求在PyQt中click一个pushButton时给它的响应槽传入一个和发射信号的对象属性相关的参数,比如按顺序创建了N个pushButton,把这个次序数i告诉槽函数。
这本来看上去是一个很简单的问题,可问题就在于QPushButton只有click()等没有任何参数的信号,而自己在写相应的槽的时候无法让其再传入一个自定义的参数。而信号发射时如果不带参数的话槽函数根本无法获知是哪个widget向它发射了信号,自然也无法处理和发射对象相关的属性。
当然这个问题还得从qt的信号和槽机制说起,槽函数必须和信号的参数是保持一致的,定义信号发射时带了几个参数,槽函数被调用时就会以这几个参数为入参,这是关键的一步。我一直觉得qt自称最大特色的信号和槽机制是个不好的发明,也许它真的很强大,但它的机制很别扭,不容易理解,这会给开发者带来困惑,就像我一样。
于是就出了这样的问题,折腾了半天终于发现其实可以用事件响应来解决这个问题。Qt中的事件响应函数是widget对象的一个方法,也就是说任何从QWidget继承的类的对象都可以侦听到诸如mousePressEvent()之类的事件。这样一来,自定义一个从QPushButton继承的类,然后在其中重写mousePressEvent()方法,在该方法中emit()一个自定义的信号或者直接执行本来要由槽函数完成的动作即可。完整代码如下:
import sys
from PyQt4 import QtCore, QtGui
class ManyButton(QtGui.QDialog):
t = ['button1', 'button2', 'button3', 'button4', 'button5']
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.pb = []
vbox = QtGui.QVBoxLayout()
for i in range(len(self.t)):
self.pb.append(MyButton(self.t[i], self))
QtCore.QObject.connect(self.pb[i], QtCore.SIGNAL("myslot(str)"), self.myslot)
vbox.addWidget(self.pb[i])
self.setLayout(vbox)
self.resize(250, 150)
def myslot(self, text):
QtGui.QMessageBox.critical(self, "MessageShow", text, QtGui.QMessageBox.Ok)
class MyButton(QtGui.QPushButton):
def __init__(self, text, parent):
QtGui.QPushButton.__init__(self, text, parent)
def mouseReleaseEvent(self, event):
self.emit(QtCore.SIGNAL("myslot(str)"), self.text())
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mb = ManyButton()
mb.show()
sys.exit(app.exec_())
从中可以看出,其实我们通常关注并以为好像是一个函数的信号本身只是一个字符串,比如上面的myslot(str),你必须将这个字符串传入QtCore.SIGNAL()来生成信号。而信号定义的格式必须和emit时所带的参数个数一致,比如
self.emit(QtCore.SIGNAL("myslot(str, str)"), self.text1(), self.text2())
信号后面就必须跟上两个参数类型str,或者不写也行,但不能写成
self.emit(QtCore.SIGNAL("myslot(str)"), self.text1(), self.text2()), 这时会报错。
直接写成这样也是能正常运行的
self.emit(QtCore.SIGNAL("myslot"), self.text1(), self.text2())
也许信号和槽的机制在由多个发射源向多个槽发射信号时会起到很大作用,不过一般来讲,感觉这种机制还是太麻烦了一点。