Why isn't this signal/slot code working - python

I have a basic signal/slot code where I want to emit a signal in one widget, and connect that signal in another widget.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PyQt5.QtWidgets import QMainWindow, QAction, QApplication, QWidget, QPushButton, qApp, QLabel, QHBoxLayout, QVBoxLayout, QSplitter, QFileDialog
from PyQt5.QtGui import QIcon, QPixmap, QPainter, QImage
from PyQt5.QtCore import QSize, Qt, pyqtSignal, QObject
import sys, random
import qdarkstyle
from os import path
class SignalFactory(QObject):
selectedTextureToLoad = pyqtSignal()
class Application(QMainWindow):
def __init__(self):
super().__init__()
self.signals = SignalFactory()
self.mainArea = MainArea()
# Option 1 - Uncomment below / Works but strong coupling between widgets
# self.signals.selectedTextureToLoad.connect(self.mainArea.doSomeStuff)
self.setCentralWidget(self.mainArea)
self.setGeometry(300, 300, 800, 400)
self.show()
def emitStuff(self):
print("Emitting...")
self.signals.selectedTextureToLoad.emit()
class MainArea(QWidget):
def __init__(self):
super().__init__()
self.signals = SignalFactory()
# Option 2 - Uncomment below / Does not work
#self.signals.selectedTextureToLoad.connect(self.doSomeStuff)
def doSomeStuff(self):
print("Receiving...")
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Application()
ex.emitStuff()
sys.exit(app.exec_())
If I uncomment Option 1, the code works, and the signal is received. However, there is a coupling between the two widgets. In this case it's fine, because one widget is the parent of the other and naturally keeps track of its child. But in a more complex scenario, that means keeping track of many widgets just to configure the signals, which is not great.
If I uncomment Option 2, the code does not work, and the console only displays "Emitting...". It's kind of annoying, since this is in my opinion the cleanest way to configure signal in one place and emit it from another place, without introducing coupling.
Am I missing something fundamental here ?
EDIT:
If you modify the code like this, by adding the returnASignal function
from PyQt5.QtWidgets import QMainWindow, QAction, QApplication, QWidget, QPushButton, qApp, QLabel, QHBoxLayout, QVBoxLayout, QSplitter, QFileDialog
from PyQt5.QtGui import QIcon, QPixmap, QPainter, QImage
from PyQt5.QtCore import QSize, Qt, pyqtSignal, QObject
import sys, random
import qdarkstyle
from os import path
def returnASignal():
print('Returning a signal')
return pyqtSignal()
class SignalFactory(QObject):
selectedTextureToLoad = returnASignal()
class Application(QMainWindow):
def __init__(self):
super().__init__()
self.signals = SignalFactory()
self.mainArea = MainArea()
# Option 2 - Uncomment below / Works but strong coupling between widgets
# self.signals.selectedTextureToLoad.connect(self.mainArea.doSomeStuff)
self.setCentralWidget(self.mainArea)
self.setGeometry(300, 300, 800, 400)
self.show()
def emitStuff(self):
print("Emitting...")
self.signals.selectedTextureToLoad.emit()
class MainArea(QWidget):
def __init__(self):
super().__init__()
self.signals = SignalFactory()
# Option 1 - Uncomment below / Does not work
self.signals.selectedTextureToLoad.connect(self.doSomeStuff)
def doSomeStuff(self):
print("Receiving...")
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Application()
ex.emitStuff()
sys.exit(app.exec_())
When running it the console displays this:
Returning a signal
Emitting...
"Returning a signal" is printed only once and not twice, which shows that although there are multiple SignalFactory instances, they all share the same selectedTextureToLoad object. This is definitely a proper static class member and not an instance variable. Therefore, the signal object is the same everywhere, and I still don't understand why Option 2 does not work.

There is no real mystery here. Signal objects behave in exactly the same way as methods defined in classes.
If you put in some debugging prints like this:
class SignalFactory(QObject):
selectedTextureToLoad = returnASignal()
print(selectedTextureToLoad)
def foo(self): pass
print(foo)
class Application(QMainWindow):
def __init__(self):
super().__init__()
self.signals = SignalFactory()
print(self.signals.selectedTextureToLoad)
print(self.signals.foo)
...
class MainArea(QWidget):
def __init__(self):
super().__init__()
self.signals = SignalFactory()
print(self.signals.selectedTextureToLoad)
print(self.signals.foo)
and run your example, it will produce output like this:
Returning a signal
<unbound PYQT_SIGNAL []>
<function SignalFactory.foo at 0x7f2a57b1c268>
<bound PYQT_SIGNAL selectedTextureToLoad of SignalFactory object at 0x7f2a57b96828>
<bound method SignalFactory.foo of <__main__.SignalFactory object at 0x7f2a57b96828>>
<bound PYQT_SIGNAL selectedTextureToLoad of SignalFactory object at 0x7f2a57b96948>
<bound method SignalFactory.foo of <__main__.SignalFactory object at 0x7f2a57b96948>>
Emitting...
As you can see, both signals and methods are bound objects when accessed from instances, and unbound objects when accessed from the class. Methods must be bound to the instance so that self can be passed as the first argument. Likewise, a signal object must be bound to the instance to ensure that connected slots only receive signals from the specific instance that sent it.
So there are two signals named selectedTextureToLoad in your example - one for each instance of SignalFactory that is created. Your example doesn't work because the doSomeStuff slot isn't connected to the specific bound signal object that emitted the selectedTextureToLoad signal.
The signal-slot mechanism is designed for communication between objects. There is no facility for broadcasting messages without a known sender, so explicit connections must always be made between the emitter and the receiver. If you want to send a more generic signal, create a global instance of SignalFactory.

Related

Execute a function after closing a window (not a child window)?

I have in my application a button which after being clicked opens up another window (which is a separate python file).
I want to execute a function after this second window has been closed. Is there a way I can capture that window's 'closed' signal or something like that?
Here is my code: (the main window)
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QColor
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout
from PyQt5.QtWidgets import QPushButton
import second_dialog # the second window I am importing
from sys import exit as sysExit
class Marker(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(350, 250)
self.openbtn = QPushButton('open')
self.openbtn.clicked.connect(self.open_dialog)
self.label_1 = QtWidgets.QLabel()
self.label_1.setGeometry(QtCore.QRect(10, 20, 150, 31))
self.label_1.setObjectName("label_1")
self.label_1.setText("HEy")
HBox = QHBoxLayout()
HBox.addWidget(self.openbtn)
HBox.addWidget(self.label_1)
HBox.addStretch(1)
VBox = QVBoxLayout()
VBox.addLayout(HBox)
VBox.addStretch(1)
self.setLayout(VBox)
def open_dialog(self):
self.dialog = QtWidgets.QWidget()
self.box = second_dialog.Marker()
self.box.show()
def do_something(self):
self.label_1.setText("Closed")
def paintEvent(self, event):
p = QPainter(self)
p.fillRect(self.rect(), QColor(128, 128, 128, 128))
if __name__ == "__main__":
MainEventThred = QApplication([])
MainApp = Marker()
MainApp.show()
MainEventThred.exec()
Here is the code for the second window:
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QColor
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout
from PyQt5.QtWidgets import QPushButton
import first_window
from sys import exit as sysExit
class Marker(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(350, 250)
self.openbtn = QPushButton('close')
self.openbtn.clicked.connect(self.Close)
HBox = QHBoxLayout()
HBox.addWidget(self.openbtn)
HBox.addStretch(1)
VBox = QVBoxLayout()
VBox.addLayout(HBox)
VBox.addStretch(1)
self.setLayout(VBox)
def Close(self):
TEST_draggable.Marker.do_something(first_window.Marker)
self.close()
def paintEvent(self, event):
p = QPainter(self)
p.fillRect(self.rect(), QColor(128, 128, 128, 128))
if __name__ == "__main__":
MainEventThred = QApplication([])
MainApp = Marker()
MainApp.show()
MainEventThred.exec()
The code does not run and throws this error:
Traceback (most recent call last):
File "some/path/second_dialog.py", line 28, in Close
TEST_draggable.Marker.do_something(first_window.Marker)
File "some/path/first_window.py", line 39, in do_something
self.label_1.setText("clicked")
AttributeError: type object 'Marker' has no attribute 'label_1'
[1] 7552 abort (core dumped) /usr/local/bin/python3
How can I fix this? I've looked up a lot of forums suspecting circular imports as the culprit but I'm not sure. Please help.
The problem has nothing to do with imports, but with the fact that you're trying to run an instance method on a class.
The "culprit" is here:
TEST_draggable.Marker.do_something(first_window.Marker)
first_window.Marker will be used for the self argument in do_something, but since it is the class, it has no label_1 attribute (only the instances of that class have that attribute). I suggest you to do some research on how classes and instances work in general, and how to deal with them in Python.
The easiest solution is to create your own signal for the second window and connect that signal on the function that will "do_something". Instead of connecting to a new function, then, we subclass the closeEvent() and send the signal from there, so that we can capture the close even when the user clicks the dedicated button on the title bar. Note that if you want to change the behavior of close you can just override it and then call the base implementation (super().close()) instead of using another function name (which should not have a capitalized name, by the way, as they should only be used for classes and constants).
This is the second class; be aware that using identical names for different classes is a very bad idea.
from PyQt5.QtCore import pyqtSignal
class Marker(QWidget):
closed = pyqtSignal()
def __init__(self):
QWidget.__init__(self)
self.resize(350, 250)
self.openbtn = QPushButton('close')
self.openbtn.clicked.connect(self.close)
# ...
def closeEvent(self, event):
self.closed.emit()
Then, in the first:
class Marker(QWidget):
# ...
def open_dialog(self):
self.dialog = QtWidgets.QWidget()
self.box = second_dialog.Marker()
self.box.closed.connect(self.do_something)
self.box.show()

PyQt5 Threads, Signal, and Slot. Connect Error

I'm new to PyQt5 and I can't seem to connect my pyqtSignal and pyqtSlot. The error, "TypeError: connect() failed between worker.newIcon[object] and updateIcon()" pops out. Anyone can guide me to the right path please?
from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QAction, QMenu
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
class worker(QThread):
newIcon = pyqtSignal(object)
def run(self):
while True:
self.newIcon.emit(QIcon("shield-off.png"))
QThread.msleep(1000)
class systemTray():
def start_tray(self):
self.app = QApplication([])
self.app.setQuitOnLastWindowClosed(False)
icon = QIcon("shield-on.png")
self.tray = QSystemTrayIcon()
self.tray.setIcon(icon)
self.tray.setVisible(True)
self.menu = QMenu()
self.quit = QAction("Exit")
self.quit.triggered.connect(self.app.quit)
self.menu.addAction(self.quit)
self.tray.setContextMenu(self.menu)
self.thread = QThread()
self.worker = worker()
self.worker.moveToThread(self.thread)
self.worker.newIcon.connect(self.updateIcon)
self.thread.started.connect(self.worker.run)
self.thread.start()
# Run tray
self.app.exec_()
#pyqtSlot(object)
def updateIcon(self, icon):
self.tray.setIcon(icon)
if __name__ == "__main__":
be_tray = systemTray()
be_tray.start_tray()
The pyqtSlot decorator is intended to be used for QObject subclasses, otherwise it will not work correctly.
So, the solution is pretty simple, as you only need to inherit from QObject:
class systemTray(QObject):
def start_tray(self):
# ...
On the other hand, using a pyqtSlot decorator is rarely needed and only for specific situations (see this related answer. In your case, it doesn't seem necessary.
Also, since you're already subclassing from QThread, you can just use its own start(), without using moveToThread() on a new one.

Usage of pyqtsignal and emit in python [duplicate]

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:
For a predefined signal: send the signal to the slot function. Slot can be reimplemented to use the signal values.
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.
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)
the accepted answer, was tricky to me to undestand because of the use of the QSlider built in signal valueChanged() I didn't know about https://doc.qt.io/qtforpython/PySide6/QtWidgets/QSlider.html, so I added my own signal that is emitted when self.sld.valueChanged.connect(self.on_sld_valueChanged) is called, to have an example of how to create my own pyqtSignal. the Post title: "PyQt proper use of emit() and pyqtSignal()" was misleading to me , I am trying to understand signals and slots too, so I wanted to add emit() to the code. I know its not the way to go forward but just to figure out how it works:
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_( ))
I'll wait to somebody that could show me how to reimplement QSlider valueChanged() signal

How to can I add threading to PyQt5 GUI?

So I have created a GUI using QT Designer. It works pretty well, but on more complex calls it doesn't update the main window and locks up. I want to run my CustomComplexFunction() while updating a textEdit in the main window from constantly changing backend information, and I wanted it to run every 2 seconds. The following code seems right and runs without errors, but doesn't update the textEdit. Please note i'm importing a .ui file designed from QT Designer with a pushButton and textEdit and the code won't run without it.
Main.py
import sys
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout, QMainWindow
from PyQt5.QtCore import QCoreApplication, QObject, QRunnable, QThread, QThreadPool, pyqtSignal, pyqtSlot
from PyQt5 import uic, QtGui
class Worker(QObject):
newParams = pyqtSignal()
#pyqtSlot()
def paramUp(self):
x=1
for x in range(100):
time.sleep(2)
self.newParams.emit()
print ("sent new params")
x +=1
Ui_somewindow, _ = uic.loadUiType("mainwindow.ui") #the path to UI
class SomeWindow(QMainWindow, Ui_somewindow, QDialog):
def __init__(self):
QMainWindow.__init__(self)
Ui_somewindow.__init__(self)
self.setupUi(self)
# Start custom functions
self.params = {}
self.pushButton.clicked.connect(self.complex) #NumEvent
def complex(self):
self.work = Worker()
self.thread = QThread()
self.work.newParams.connect(self.changeTextEdit)
self.work.moveToThread(self.thread)
self.thread.start()
self.CustomComplexFunction()
def CustomComplexFunction(self):
self.params['City'] = 'Test'
def changeTextEdit(self):
try:
City = self.params['City']
self.textEdit_14.setPlainText(City)
except KeyError:
City = None
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SomeWindow()
window.show()
sys.exit(app.exec_())
You can see the official docs for Signals and Slots here and this SO post was also very helpful, but it seems like I built it correctly. According to the docs, the emitter doesn't care if the signal is used. This might be why the code doesn't have errors but doesn't work either.
Any ideas on how to make it work? Or atleast some way to test the emitter and signals??
You have forgot to connect the thread to the worker object.
self.work = Worker()
self.thread = QThread()
self.thread.started.connect(self.worker.work) # <--new line, make sure work starts.
self.thread.start()
Good luck with the application :-)

PyQt5 signal-slot decorator example

I am currently in the process of creating a class that produces a pyqtSignal(int) and pyqtSlot(int). The difficulty lies in creating a signal that emits a specific value.
Suppose I want to produce something similar to the following simple example:
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_())
My first question for the above code is:
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?
For specific reasons, I would like to produce my own signal using the pyqtSignal() factory and am given decent documentation here. The only problem however, is that the very simple example does not give a solid foundation for how to emit specific signals.
Here is what I am trying to do but have found myself lost:
Create a metaclass that allows for the implementation of many different types of QWidget subclasses.
Have the metaclass produce its own signal that can be called from outside the class.
This is what I am going for:
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
As you can see. I give the widget a name because I find this to be useful, and I also try to instantiate the QWidget from within the class to simplify code.
This is how I would like a main class to produce the widgets from the first example:
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_())
There are only 2 problems that need to be solved here:
The template metaclass is set up incorrectly, and I am unable to instantiate the QWidget from within the metaclass. The Q_Type is telling me that its type is PyQt5.QtCore.pyqtWrapperType when it should be PyQt5.QtWidgets.QSlider.
Even though I am creating a newSignal via the templated class, how do I get the QSlider to send the changed value that I want it to send. I believe this is where emit() comes into play but do not have enough knowledge as to how it is useful.
I know I could make some sort of function call self.sld.emit(35) if I would like the signal to pass the value 35 to the slot function. The question becomes not how but where should I implement this function?
I may be totally offbase and overthinking the solution, feel free to correct my code so that I can have my signal emit the value of the slider.
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?
Already addressed in your previous question, but I'll re-iterate again.
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.
Also, as addressed in your previous question, you cannot create signals as instance variables, they must be class attributes.
This will never work
def setSignal(self,type):
self.signal = pyqtSignal(type)
It must be
class MyWidget(QWidget):
signal = pyqtSignal(int)
Create a metaclass that allows for the implementation of many different types of QWidget subclasses.
You cannot define a metaclass for QObjects, since they already use their own metaclass (this is what allows the signal magic to work). However, you can still make a QObject subclass factory using the type function.
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.
In short, don't do this. You shouldn't be emitting signals from outside the class. The factory example above isn't very useful, because you're not defining custom methods on those classes to emit those signals. Typically, this is how you would utilize custom signals
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

Categories