PyQt 正确使用 emit() 和 pyqtSignal()

2022-01-12 00:00:00 python pyqt pyqt5 signals signals-slots

问题描述

我正在阅读有关 PyQt5 的一些文档,以提出一个简单的信号槽机制.由于设计考虑,我已经停下来了.

考虑以下代码:

导入系统从 PyQt5.QtCore 导入 (Qt, pyqtSignal)从 PyQt5.QtWidgets 导入(QWidget、QLCDNumber、QSlider、QVBoxLayout, QApplication)类示例(QWidget):def __init__(self):超级().__init__()self.initUI()def printLabel(self, str):打印(字符串)定义日志标签(自我,str):'''记录到文件'''经过定义 initUI(自我):lcd = QLCDNumber(self)sld = QSlider(Qt.Horizo​​ntal, self)vbox = QVBoxLayout()vbox.addWidget(lcd)vbox.addWidget(sld)self.setLayout(vbox)#冗余连接sld.valueChanged.connect(lcd.display)sld.valueChanged.connect(self.printLabel)sld.valueChanged.connect(self.logLabel)self.setGeometry(300, 300, 250, 150)self.setWindowTitle('信号和槽')自我展示()如果 __name__ == '__main__':应用程序 = QApplication(sys.argv)ex = 示例()sys.exit(app.exec_())

要跟踪对滑块所做的更改,我只需打印并记录所做的更改.我不喜欢代码的地方是我需要调用 sld.valueChanged 插槽三次以将相同的信息发送到 3 个不同的插槽.

是否可以创建我自己的 pyqtSignal 将整数发送到单个插槽函数.反过来让槽函数发出需要进行的更改?

  • 也许我不完全理解 emit() 的用途,因为在 PyQt Signal-Slot 文档.我们给出的只是一个如何实现不带参数的 emit 的示例.

我想做的是创建一个处理发射函数的函数.考虑以下几点:

导入系统从 PyQt5.QtCore 导入 (Qt, pyqtSignal)从 PyQt5.QtWidgets 导入(QWidget、QLCDNumber、QSlider、QVBoxLayout, QApplication)类示例(QWidget):def __init__(self):超级().__init__()#创建信号self.val_Changed = pyqtSignal(int, name='valChanged')self.initUI()定义 initUI(自我):lcd = QLCDNumber(self)sld = QSlider(Qt.Horizo​​ntal, 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('信号和槽')自我展示()def handle_LCD(自我,文本):'''日志'''打印(文本)'''将 val_Changed 连接到 lcd.display'''如果 __name__ == '__main__':应用程序 = QApplication(sys.argv)ex = 示例()sys.exit(app.exec_())

这里显然存在一些严重的设计缺陷.我无法理解函数调用的顺序.而且我没有正确实现 pyqtSignal .不过,我确实相信正确陈述以下 3 点将有助于我制作出合适的应用程序:

  1. 对于预定义的信号:将信号发送到槽函数.可以重新实现 Slot 以使用信号值.
  2. 生成带有一些参数的 pyqtSignal 对象.目前尚不清楚这些参数的用途是什么以及它们与发射"参数有何不同.
  3. emit 可以重新实现以将特定的信号值发送到槽函数.目前还不清楚为什么我需要发送与以前存在的信号方法不同的值.

随意完全改变我正在尝试做的代码,因为我还没有弄清楚它是否属于良好的风格.

解决方案

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

类示例(QWidget):def __init__(self):超级().__init__()self.initUI()def printLabel(self, str):打印(字符串)定义日志标签(自我,str):'''记录到文件'''经过@QtCore.pyqtSlot(int)def on_sld_valueChanged(自我,价值):self.lcd.display(值)self.printLabel(值)self.logLabel(值)定义 initUI(自我):self.lcd = QLCDNumber(self)self.sld = QSlider(Qt.Horizo​​ntal, 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('信号和槽')

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

类示例(QWidget):my_signal = pyqtSignal(int)

pyqtSignal 的参数定义了将在该信号上发出的对象类型,因此在这种情况下,您可以这样做

self.my_signal.emit(1)

<块引用>

emit 可以重新实现以将特定的信号值发送到插槽功能.目前还不清楚为什么我需要发送不同的来自先前存在的信号方法的值.

您通常不应该发出内置信号.您应该只需要发出您定义的信号.定义信号时,可以定义不同类型的不同签名,插槽可以选择要连接的签名.例如,您可以这样做

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

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

@pyqtSlot(int)def on_my_signal_int(自我,价值):断言 isinstance(值,int)@pyqtSlot(str)def on_my_signal_str(自我,价值):断言 isinstance(值,str)

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

I am reading through some documentation on PyQt5 to come up with a simple signal-slot mechanism. I have come to a halt due to a design consideration.

Consider the following code:

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_())

To track the changes made to the slider, I simply print and log the changes made. What I do not like about the code is that I am required to call the sld.valueChanged slot thrice to send the same information to 3 different slots.

Is it possible to create my own pyqtSignal that sends an integer to a single slot function. And in turn have the slot function emit the changes that need to be made?

  • Maybe I don't fully understand the purpose of emit() because there are no good examples of it's purpose in the PyQt Signal-Slot docs. All we're given is an example of how to implement an emit with no parameters.

What I would like to do is create a function that handles the emit function. Consider the following:

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_())

There are obviously some serious design flaws here. I cannot wrap my head around the order of function calls. And I am not implementing pyqtSignal correctly. I do however believe that correctly stating the following 3 points will help me produce a proper app:

  1. For a predefined signal: send the signal to the slot function. Slot can be reimplemented to use the signal values.
  2. Produce pyqtSignal object with some parameters. It is not yet clear what the purpose of these parameters are and how they differ from 'emit' parameters.
  3. emit can be reimplemented to send specific signal values to the slot function. It is also not yet clear why I would need to send different values from previously existing signal methods.

Feel free to completely alter the code for what I am trying to do because I have not yet figured out if its in the realm of good style.

解决方案

You can define your own slot (any python callable) and connect that to the signal, then call the other slots from that one slot.

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')

Also, if you want to define your own signals, they have to be defined as class variables

class Example(QWidget):
    my_signal = pyqtSignal(int)

The arguments to pyqtSignal define the types of objects that will be emit'd on that signal, so in this case, you could do

self.my_signal.emit(1)

emit can be reimplemented to send specific signal values to the slot function. It is also not yet clear why I would need to send different values from previously existing signal methods.

You generally shouldn't be emitting the built in signals. You should only need to emit signals that you define. When defining a signal, you can define different signatures with different types, and slots can choose which signature they want to connect to. For instance, you could do this

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

This will define a signal with two different signatures, and a slot could connect to either one

@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)

In practice, I rarely overload signal signatures. I would normally just create two separate signals with different signatures rather than overloading the same signal. But it exists and is supported in PyQt because Qt has signals that are overloaded this way (eg. QComboBox.currentIndexChanged)

相关文章