PyQt5 信号槽装饰器示例
PyQt5 signal-slot decorator example
我目前正在创建一个生成 pyqtSignal(int) 和 pyqtSlot(int) 的 class。难点在于创建一个发出特定值的信号。
假设我想生成类似于以下简单示例的内容:
import sys
from PyQt5.QtCore import (Qt, pyqtSignal, pyqtSlot)
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)
@pyqtSlot(int)
def on_sld_valueChanged(self, value):
self.lcd.display(value)
self.printLabel(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')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
我对上面代码的第一个问题是:
- 删除
pyqtSlot(int)
对代码没有影响时,为什么还要使用 pyqtSlot() 装饰器?
- 你能举例说明什么时候需要吗?
出于特定原因,我想使用 pyqtSignal() 工厂生成我自己的信号,并获得了不错的文档here. The only problem however, is that the very simple example没有为如何发出特定信号提供坚实的基础。
这是我正在尝试做但发现自己迷路的事情:
- 创建一个允许实现许多不同类型的 QWidget 子classes 的元class。
- 让元class产生自己的信号,可以从class外部调用。
这就是我想要的:
from PyQt5.QtWidgets import QPushButton, QWidget
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QSlider
def template(Q_Type, name: str, *args):
class MyWidget(Q_Type):
def __init__(self) -> None:
super().__init__(*args)
self._name = name
def setSignal(self,type):
self.signal = pyqtSignal(type)
def callSignal(self):
pass
return MyWidget
如你所见。我给小部件起了一个名字,因为我发现这很有用,而且我还尝试从 class 中实例化 QWidget 以简化代码。
这就是我希望 main class 生成第一个示例中的小部件的方式:
import sys
from PyQt5.QtCore import (Qt, pyqtSignal, pyqtSlot)
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)
@pyqtSlot(int)
def sld_valChanged(self, value):
self.lcd.display(value)
self.printLabel(value)
def initUI(self):
#instantiate the QWidgets through template class
self.lcd = template(QLCDNumber,'lcd_display')
self.sld = template(QSlider, 'slider', Qt.Horizontal)
#create signal
#self.sld.setSignal(int)
vbox = QVBoxLayout()
vbox.addWidget(self.lcd)
vbox.addWidget(self.sld)
self.setLayout(vbox)
#connect signal - this won't send the value of the slider
#self.sld.signal.connect(self.sld_valChanged)
self.sld.valueChanged.connect(self.sld_valChanged)
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_())
这里只有2个问题需要解决:
- 模板 metaclass 设置不正确,我无法从 metaclass 中实例化 QWidget。
Q_Type
告诉我它的类型是 PyQt5.QtCore.pyqtWrapperType
而它应该是 PyQt5.QtWidgets.QSlider
.
- 即使我通过模板 class 创建了一个新信号,我如何让
QSlider
发送我希望它发送的更改值。我相信这是 emit()
发挥作用的地方,但没有足够的知识知道它是如何有用的。
我知道如果我希望信号将值 35 传递给槽函数,我可以进行某种函数调用 self.sld.emit(35)
。问题变成了我应该在哪里实现这个功能而不是如何实现这个功能?
我的解决方案可能完全离谱且想得太多了,请随时更正我的代码,以便我的信号发出滑块的值。
Why use the pyqtSlot() decorator at all when removing pyqtSlot(int)
has no effect on the code? Can you give an example of when it would be
necessary?
已在您的 中解决,但我会再次重申。
Although PyQt4 allows any Python callable to be used as a slot when
connecting signals, it is sometimes necessary to explicitly mark a
Python method as being a Qt slot and to provide a C++ signature for
it. PyQt4 provides the pyqtSlot() function decorator to do this.
Connecting a signal to a decorated Python method also has the
advantage of reducing the amount of memory used and is slightly
faster.
此外,正如您在 中所述,您不能将信号创建为实例变量,它们必须是 class 属性 。
这永远行不通
def setSignal(self,type):
self.signal = pyqtSignal(type)
一定是
class MyWidget(QWidget):
signal = pyqtSignal(int)
Create a metaclass that allows for the implementation of many different types of QWidget subclasses.
您不能为 QObjects
定义元class,因为它们已经使用了自己的元class(这就是信号魔法起作用的原因)。但是,您仍然可以使用 type
函数创建 QObject
subclass 工厂。
def factory(QClass, cls_name, signal_type):
return type(cls_name, (QClass,), {'signal': pyqtSignal(signal_type)})
MySlider = factory(QSlider, 'MySlider', int)
self.sld = MySlider(Qt.Horizontal)
self.sld.signal.connect(self.on_signal)
Have the metaclass produce its own signal that can be called from outside the class.
简而言之,不要这样做。你不应该从 class 之外发出信号。上面的工厂示例不是很有用,因为您没有在那些 classes 上定义自定义方法来发出这些信号。通常,这就是您使用自定义信号的方式
class MyWidget(QWidget):
colorChanged = pyqtSignal()
def setColor(self, color):
self._color = color
self.colorChanged.emit()
class ParentWidget(QWidget):
def __init__(self):
...
self.my_widget = MyWidget(self)
self.my_widget.colorChanged.connect(self.on_colorChanged)
# This will cause the colorChanged signal to be emitted, calling on_colorChanged
self.my_widget.setColor(Qt.blue)
def on_colorChanged(self):
# do stuff
我目前正在创建一个生成 pyqtSignal(int) 和 pyqtSlot(int) 的 class。难点在于创建一个发出特定值的信号。
假设我想生成类似于以下简单示例的内容:
import sys
from PyQt5.QtCore import (Qt, pyqtSignal, pyqtSlot)
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)
@pyqtSlot(int)
def on_sld_valueChanged(self, value):
self.lcd.display(value)
self.printLabel(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')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
我对上面代码的第一个问题是:
- 删除
pyqtSlot(int)
对代码没有影响时,为什么还要使用 pyqtSlot() 装饰器? - 你能举例说明什么时候需要吗?
出于特定原因,我想使用 pyqtSignal() 工厂生成我自己的信号,并获得了不错的文档here. The only problem however, is that the very simple example没有为如何发出特定信号提供坚实的基础。
这是我正在尝试做但发现自己迷路的事情:
- 创建一个允许实现许多不同类型的 QWidget 子classes 的元class。
- 让元class产生自己的信号,可以从class外部调用。
这就是我想要的:
from PyQt5.QtWidgets import QPushButton, QWidget
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QSlider
def template(Q_Type, name: str, *args):
class MyWidget(Q_Type):
def __init__(self) -> None:
super().__init__(*args)
self._name = name
def setSignal(self,type):
self.signal = pyqtSignal(type)
def callSignal(self):
pass
return MyWidget
如你所见。我给小部件起了一个名字,因为我发现这很有用,而且我还尝试从 class 中实例化 QWidget 以简化代码。
这就是我希望 main class 生成第一个示例中的小部件的方式:
import sys
from PyQt5.QtCore import (Qt, pyqtSignal, pyqtSlot)
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)
@pyqtSlot(int)
def sld_valChanged(self, value):
self.lcd.display(value)
self.printLabel(value)
def initUI(self):
#instantiate the QWidgets through template class
self.lcd = template(QLCDNumber,'lcd_display')
self.sld = template(QSlider, 'slider', Qt.Horizontal)
#create signal
#self.sld.setSignal(int)
vbox = QVBoxLayout()
vbox.addWidget(self.lcd)
vbox.addWidget(self.sld)
self.setLayout(vbox)
#connect signal - this won't send the value of the slider
#self.sld.signal.connect(self.sld_valChanged)
self.sld.valueChanged.connect(self.sld_valChanged)
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_())
这里只有2个问题需要解决:
- 模板 metaclass 设置不正确,我无法从 metaclass 中实例化 QWidget。
Q_Type
告诉我它的类型是PyQt5.QtCore.pyqtWrapperType
而它应该是PyQt5.QtWidgets.QSlider
. - 即使我通过模板 class 创建了一个新信号,我如何让
QSlider
发送我希望它发送的更改值。我相信这是emit()
发挥作用的地方,但没有足够的知识知道它是如何有用的。 我知道如果我希望信号将值 35 传递给槽函数,我可以进行某种函数调用self.sld.emit(35)
。问题变成了我应该在哪里实现这个功能而不是如何实现这个功能?
我的解决方案可能完全离谱且想得太多了,请随时更正我的代码,以便我的信号发出滑块的值。
Why use the pyqtSlot() decorator at all when removing pyqtSlot(int) has no effect on the code? Can you give an example of when it would be necessary?
已在您的
Although PyQt4 allows any Python callable to be used as a slot when connecting signals, it is sometimes necessary to explicitly mark a Python method as being a Qt slot and to provide a C++ signature for it. PyQt4 provides the pyqtSlot() function decorator to do this.
Connecting a signal to a decorated Python method also has the advantage of reducing the amount of memory used and is slightly faster.
此外,正如您在
这永远行不通
def setSignal(self,type):
self.signal = pyqtSignal(type)
一定是
class MyWidget(QWidget):
signal = pyqtSignal(int)
Create a metaclass that allows for the implementation of many different types of QWidget subclasses.
您不能为 QObjects
定义元class,因为它们已经使用了自己的元class(这就是信号魔法起作用的原因)。但是,您仍然可以使用 type
函数创建 QObject
subclass 工厂。
def factory(QClass, cls_name, signal_type):
return type(cls_name, (QClass,), {'signal': pyqtSignal(signal_type)})
MySlider = factory(QSlider, 'MySlider', int)
self.sld = MySlider(Qt.Horizontal)
self.sld.signal.connect(self.on_signal)
Have the metaclass produce its own signal that can be called from outside the class.
简而言之,不要这样做。你不应该从 class 之外发出信号。上面的工厂示例不是很有用,因为您没有在那些 classes 上定义自定义方法来发出这些信号。通常,这就是您使用自定义信号的方式
class MyWidget(QWidget):
colorChanged = pyqtSignal()
def setColor(self, color):
self._color = color
self.colorChanged.emit()
class ParentWidget(QWidget):
def __init__(self):
...
self.my_widget = MyWidget(self)
self.my_widget.colorChanged.connect(self.on_colorChanged)
# This will cause the colorChanged signal to be emitted, calling on_colorChanged
self.my_widget.setColor(Qt.blue)
def on_colorChanged(self):
# do stuff