使用存储在 python 字典中的信号

Use signals stored in a python dict

我想动态创建然后操作大量小部件。我的想法是将小部件存储在字典 (mywidgets) 中,并使用存储在另一个字典 (mysignals) 中的信号触发它们。两个字典共享列表(名称)中定义的相同键,字典用 for 循环初始化。

当我将信号连接到插槽时,我目前正面临一个 AttributeError:'PyQt5.QtCore.pyqtSignal' 对象没有属性 'connect'。

我尝试禁用 signal/slot 连接:GUI 看起来不错,QLineEdit 很好地存储在 mywidgets 中。 mysignals 项的类型是正确的:class 'PyQt5.QtCore.pyqtSignal'.

能否请您解释一下问题出在哪里? 谢谢

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QPushButton, QVBoxLayout
from PyQt5.QtCore import pyqtSlot, pyqtSignal

class App(QWidget):

    names = ["foo","bar"]
    mysignals = {}    # Store several signals in a dict
    for name in names:
        mysignals[name] = pyqtSignal(str)

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

        # Create Widgets
        self.btn_go = QPushButton("Go")     #Simple push button
        self.mywidgets = {}                   #Store several QLineEdit in a dict
        for name in self.names:
            self.mywidgets[name] = QLineEdit()

        # Connect signals
        self.btn_go.clicked.connect(self.on_click)                  #Connect push button
        for name in self.names:
            print(type(self.mysignals[name]))
            self.mysignals[name].connect(self.mywidgets[name].setText)  #Connect several signals

        # Configure layout
        layout = QVBoxLayout()
        layout.addWidget(self.btn_go)
        for name in self.names:
            layout.addWidget(self.mywidgets[name])
        self.setLayout(layout) 

        # Show widget
        self.show()


    @pyqtSlot()
    def on_click(self):
        data = {"foo":"Python3","bar":"PyQt5"}
        for key,value in data.items():
            self.mysignals[key].emit(value)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

预期的结果是在单击按钮时在 mywidgets["foo"] 和 mywidgets["bar"] QLineEdit 小部件中分别显示 Python3 和 PyQt5。

抱歉,我认为您为了获得预期结果而将算法复杂化了。试一试:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QPushButton, QVBoxLayout
from PyQt5.QtCore    import pyqtSlot, pyqtSignal

class App(QWidget):
    def __init__(self, names):
        super().__init__()

        self.names = names
        layout = QVBoxLayout()

        # Create Widgets
        self.btn_go = QPushButton("Go")            # Simple push button
        self.btn_go.clicked.connect(self.on_click) # Connect push button
        layout.addWidget(self.btn_go)

        self.mywidgets = {}                        # Store several QLineEdit in a dict
        for name in self.names:
            self.mywidgets[name] = QLineEdit()    
            layout.addWidget(self.mywidgets[name])

        self.setLayout(layout) 

    @pyqtSlot()
    def on_click(self):
        data = {"foo":"Python3", "bar":"PyQt5"}
        for key, value in data.items():
            self.mywidgets[key].setText(value)

if __name__ == '__main__':
    app = QApplication(sys.argv)

    names = ["foo", "bar"]
    ex = App(names)

    ex.show()
    sys.exit(app.exec_())

正如 docs 指出的那样:

A signal (specifically an unbound signal) is a class attribute. When a signal is referenced as an attribute of an instance of the class then PyQt5 automatically binds the instance to the signal in order to create a bound signal. This is the same mechanism that Python itself uses to create bound methods from class functions.

信号被声明为 class 的属性,但是当通过自身引用时,与对象进行了绑定,即声明的信号与实例化的信号不同:

from PyQt5 import QtCore

class Foo(QtCore.QObject):
    fooSignal = QtCore.pyqtSignal()
    print("declared:", fooSignal)

    def __init__(self, parent=None):
        super(Foo, self).__init__(parent)
        print("instantiated:", self.fooSignal)

if __name__ == '__main__':
    import sys
    app = QtCore.QCoreApplication(sys.argv)
    obj = Foo()

输出:

declared: <unbound PYQT_SIGNAL )>
instantiated: <bound PYQT_SIGNAL fooSignal of Foo object at 0x7f4beb998288>

这就是你收到错误的原因,所以如果你想使用信号,你必须使用对象获取它,这样我们才能检查属性并获取信号:

from PyQt5 import QtCore, QtGui, QtWidgets

class Widget(QtWidgets.QWidget):
    foo = QtCore.pyqtSignal(str)
    bar = QtCore.pyqtSignal(str)

    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)
        self.fill_signals()
        self.names = ["foo", "bar"]

        self.btn_go = QtWidgets.QPushButton("Go")
        self.mywidgets = {}
        for name in self.names:
            self.mywidgets[name] = QtWidgets.QLineEdit()
            signal = self.mysignals.get(name)
            if signal is not None:
                signal.connect(self.mywidgets[name].setText)

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.btn_go)
        for name in self.names:
            layout.addWidget(self.mywidgets[name])

        self.btn_go.clicked.connect(self.testing)

    def fill_signals(self):
        self.mysignals = dict()
        for p in dir(self):
            attr = getattr(self, p)
            if isinstance(attr, QtCore.pyqtBoundSignal):
                self.mysignals[p] = attr

    def testing(self):
        self.foo.emit("foo")
        self.bar.emit("bar")

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())