PyQt中emit()和pyqtSignal()的正确使用方法

44
我正在阅读一些关于PyQt5的文档,以设计一个简单的信号槽机制。由于设计考虑,我已经停滞不前。
请考虑以下代码:
import sys
from PyQt5.QtCore import (Qt, pyqtSignal)
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
    QVBoxLayout, QApplication)


class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()

    def printLabel(self, str):
        print(str)

    def logLabel(self, str):
        '''log to a file'''
        pass

    def initUI(self):

        lcd = QLCDNumber(self)
        sld = QSlider(Qt.Horizontal, self)

        vbox = QVBoxLayout()
        vbox.addWidget(lcd)
        vbox.addWidget(sld)

        self.setLayout(vbox)

        #redundant connections
        sld.valueChanged.connect(lcd.display)
        sld.valueChanged.connect(self.printLabel)
        sld.valueChanged.connect(self.logLabel)

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Signal & slot')
        self.show()


if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

为了追踪滑块所做的更改,我只需打印和记录所做的更改。我不喜欢代码的一点是我需要三次调用sld.valueChanged槽来将相同的信息发送到3个不同的槽中。
是否可以创建自己的pyqtSignal,将整数发送到单个槽函数中。反过来,槽函数发出需要进行的更改?
也许我没有完全理解emit()的目的,因为PyQt Signal-Slot docs中没有很好的例子说明其目的。我们只是给出了一个如何实现没有参数的emit的示例。
我想要做的是创建一个处理emit函数的函数。考虑以下内容:
import sys
from PyQt5.QtCore import (Qt, pyqtSignal)
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
    QVBoxLayout, QApplication)


class Example(QWidget):

    def __init__(self):
        super().__init__()

        #create signal
        self.val_Changed = pyqtSignal(int, name='valChanged')

        self.initUI()

    def initUI(self):

        lcd = QLCDNumber(self)
        sld = QSlider(Qt.Horizontal, self)

        vbox = QVBoxLayout()
        vbox.addWidget(lcd)
        vbox.addWidget(sld)

        self.setLayout(vbox)

        sld.val_Changed.connect(self.handle_LCD)
        self.val_Changed.emit()

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Signal & slot')
        self.show()

    def handle_LCD(self, text):
        '''log'''
        print(text)
        '''connect val_Changed to lcd.display'''

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

这里显然存在一些严重的设计缺陷。我无法理解函数调用的顺序,而且我没有正确实现pyqtSignal。但是,我相信正确地说明以下3点将有助于我制作出适当的应用程序:
  1. 对于预定义的信号:将信号发送到槽函数。可以重新实现槽以使用信号值。
  2. 使用一些参数生成pyqtSignal对象。目前尚不清楚这些参数的目的以及它们与“发射”参数的区别。
  3. emit可以重新实现,以将特定的信号值发送到槽函数。还不清楚为什么需要发送与先前存在的信号方法不同的值。
如果您认为需要完全改变我的代码,那也没关系,因为我还没有确定它是否符合良好的风格要求。
2个回答

60

您可以定义自己的插槽(任何 Python 可调用对象),并将其连接到信号,然后从该插槽调用其他插槽。

class Example(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def printLabel(self, str):
        print(str)

    def logLabel(self, str):
        '''log to a file'''
        pass

    @QtCore.pyqtSlot(int)
    def on_sld_valueChanged(self, value):
        self.lcd.display(value)
        self.printLabel(value)
        self.logLabel(value)

    def initUI(self):

        self.lcd = QLCDNumber(self)
        self.sld = QSlider(Qt.Horizontal, self)

        vbox = QVBoxLayout()
        vbox.addWidget(self.lcd)
        vbox.addWidget(self.sld)

        self.setLayout(vbox)
        self.sld.valueChanged.connect(self.on_sld_valueChanged)


        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Signal & slot')

另外,如果您想定义自己的信号,它们必须被定义为类变量。

class Example(QWidget):
    my_signal = pyqtSignal(int)
pyqtSignal 的参数定义了将会在该信号上发出的对象类型,因此在这种情况下,你可以这样做:
self.my_signal.emit(1)

emit可以重新实现以向槽函数发送特定的信号值。目前还不清楚为什么我需要发送与先前存在的信号方法不同的值。

通常情况下,你不应该发出内置信号。你只需要发出自己定义的信号即可。在定义信号时,你可以定义不同类型的不同签名,槽函数可以选择连接哪个签名。例如,你可以这样做:

my_signal = pyqtSignal([int], [str])

这将定义一个具有两个不同签名的信号,插槽可以连接到任何一个签名

@pyqtSlot(int)
def on_my_signal_int(self, value):
    assert isinstance(value, int)

@pyqtSlot(str)
def on_my_signal_str(self, value):
    assert isinstance(value, str)

实际上,我很少重载信号签名。通常我会创建两个具有不同签名的单独信号,而不是重载同一个信号。但是因为Qt存在这种方式的重载信号(例如QComboBox.currentIndexChanged),所以它存在并得到了PyQt的支持。


1
似乎注释掉@pyqtSlot(int)不会影响输出。为什么会这样? - Max
1
看起来装饰器的真正目的是为了允许重载。 - Max
7
这不仅适用于重载。这个答案解释得很清楚。如果您需要在线程之间发送信号,也需要使用slot装饰器。 - Brendan Abel
一个简单的侧记:因为my_signal是类的属性,而不是与任何QWidget相关联。对于创建类似于self.lcd.my_signal.connect(on_my_signal)这样的东西,什么是良好的Qt风格? - Max
如果要向LCD部件添加自定义信号,您需要将其子类化并将其添加为类属性。您还不应从任何地方发出该信号,而只能从类内部发出,因此将信号添加到非子类化部件中实际上是没有意义的。 - Brendan Abel
显示剩余3条评论

0

被接受的答案对我来说很棘手,因为使用了QSlider内置的信号valueChanged(),我不知道https://doc.qt.io/qtforpython/PySide6/QtWidgets/QSlider.html,所以我添加了自己的信号。当调用self.sld.valueChanged.connect(self.on_sld_valueChanged)时会发出该信号,以便我可以了解如何创建自己的pyqtSignal。帖子标题:“PyQt emit()和pyqtSignal()的正确使用”对我具有误导性,因为我也在尝试理解信号和槽,所以我想添加emit()到代码中。我知道这不是前进的方式,但只是为了弄清楚它是如何工作的:

import sys

from PyQt5 import QtCore

from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider,
    QVBoxLayout, QApplication)

class Example(QWidget):

    #create signal
    val_Changed = pyqtSignal(int, str, name='valChanged')
    
    def __init__(self):
        super().__init__()
        self.initUI()
        
        
        self.valChanged.connect(self.mine)
        
        self.show()

    def printLabel(self, str):
        print(str)

    def logLabel(self, str):
        '''log to a file'''
        pass


    @QtCore.pyqtSlot(int, str)
    def mine(self, value, string):
        self.lcd.display(value)
        self.printLabel((str(value)+' '+ string))
        self.logLabel(value)
        
        
    def on_sld_valueChanged(self, value):
        
        self.val_Changed.emit(value, 'using-slider')


    def initUI(self):

        self.lcd = QLCDNumber(self)
        self.sld = QSlider(Qt.Horizontal, self)

        vbox = QVBoxLayout()
        vbox.addWidget(self.lcd)
        vbox.addWidget(self.sld)

        self.setLayout(vbox)
        self.sld.valueChanged.connect(self.on_sld_valueChanged)


        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Signal & slot')
        
if __name__ ==  '__main__' :

    app = QApplication( sys.argv ) 
    ex = Example() 
    
    sys.exit(app.exec_( ))

我会等待有人向我展示如何重新实现QSlider的valueChanged()信号


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接