pyQt5: Cannot connect QSpinBox::valueChanged(int) - python

I am new to Python and Qt. Currently, I am trying to build the UI for a larger application, but I am running into problems regarding signals and slots.
Here is my code:
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QObject, pyqtSlot
import sys
class Ui_configDialog(QtWidgets.QDialog):
def __init__(self):
super(Ui_configDialog, self).__init__()
self.setupUi()
def setupUi(self):
self.setObjectName("configDialog")
self.setWindowModality(QtCore.Qt.WindowModal)
self.resize(425, 380)
row1 = DataRow(self)
self.show()
class DataRow:
def __init__(self, dialog):
rect = QtCore.QRect(10, 40, 91, 30)
self.text_fRep = QtWidgets.QSpinBox(dialog)
self.text_fRep.setGeometry(rect.translated(100, 0))
self.connect_signal()
#pyqtSlot(int)
def fRep_changed(self, value):
print(value)
def connect_signal(self):
self.text_fRep.valueChanged.connect(self.fRep_changed)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
dialog = Ui_configDialog()
sys.exit(app.exec_())
What I am trying to achieve is, that the slot fRep_changed is called whenever the value of the QSpinBox object is changed. But with this code I receive a compile error:
QObject::connect: Cannot connect QSpinBox::valueChanged(int) to (null)::fRep_changed(int)
TypeError: connect() failed between valueChanged(int) and fRep_changed()
I cannot see, why I shouldn't be able to connect the signal to the slot.
I also removed the #pyqtSlot(int). The application starts, but nothing happens when the value is changed.
Thank you for your help in advance!

Your code has 2 errors, the first one is that the slots are only implemented within the classes that inherit from QObject, so the easiest thing is for your class to inherit from QObject. The second you will see after making the previous change, it will happen that even if you change the value of QSpinBox will never be called to the slot, and this happens because the collector deletes the object of row1 of the DataRow class, the solution is simple, you just have to make row a member of the class through self, ie change row1 by self.row1
class Ui_configDialog(QtWidgets.QDialog):
def __init__(self):
super(Ui_configDialog, self).__init__()
self.setupUi()
def setupUi(self):
self.setObjectName("configDialog")
self.setWindowModality(QtCore.Qt.WindowModal)
self.resize(425, 380)
self.row1 = DataRow(self)
self.show()
class DataRow(QObject):
def __init__(self, dialog, parent=None):
QObject.__init__(self, parent)
rect = QtCore.QRect(10, 40, 91, 30)
self.text_fRep = QtWidgets.QSpinBox(dialog)
self.text_fRep.setGeometry(rect.translated(100, 0))
self.connect_signal()
#pyqtSlot(int)
def fRep_changed(self, value):
print(value)
def connect_signal(self):
self.text_fRep.valueChanged.connect(self.fRep_changed)

Related

Pyqt5 - how to go back to hided Main Window from Secondary Window?

If I click Back from the second window, the program will just exit. How do I go back to mainwindow in this case? I assume I will need some more code in that clickMethodBack function.
import os
import PyQt5
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QWidget, QLabel, QPushButton
import time
from PyQt5.QtCore import QSize
class GUI_Window():
def __init__(self):
self.main_window()
return
def main_window(self):
app = PyQt5.QtWidgets.QApplication(sys.argv)
self.MainWindow = MainWindow_()
self.MainWindow.show()
app.exec_()
return
class MainWindow_(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.TestAButton = QPushButton("TestA", self)
self.TestAButton.clicked.connect(self.TestA_clickMethod)
self.TestAButton.move(20, 0)
self.CloseButton = QPushButton("Close", self)
self.CloseButton.clicked.connect(self.Close_clickMethod)
self.CloseButton.move(20, 40)
self.TestingA = TestA_MainWindow()
def TestA_clickMethod(self):
self.TestAButton.setEnabled(False)
time.sleep(0.2)
self.TestingA.show()
self.hide()
try:
if self.TestingA.back == True:
self.show()
except:
None
def Close_clickMethod(self):
self.Test_Choice = 'Exit'
self.close()
class TestA_MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setMinimumSize(QSize(980,700))
self.setWindowTitle("TestA")
self.Back_Button = False
self.closeButton = QPushButton("Close", self)
self.closeButton.clicked.connect(self.clickMethodClose)
self.returnButton = QPushButton("Back", self)
self.returnButton.clicked.connect(self.clickMethodBack)
self.returnButton.move(0,30)
def clickMethodClose(self):
self.Back_Button = False
self.close()
def clickMethodBack(self):
self.returnButton.setEnabled(False)
time.sleep(0.5)
self.back = True
self.close()
# Run if Script
if __name__ == "__main__":
main = GUI_Window() # Initialize GUI
Your code has two very important issues.
you're using a blocking function, time.sleep; Qt, as almost any UI toolkit, is event driven, which means that it has to be able to constantly receive and handle events (coming from the system or after user interaction): when something blocks the event queue, it completely freezes the whole program until that block releases control;
you're checking for the variable too soon: even assuming the sleep would work, you cannot know if the window is closed after that sleep timer has ended;
The solution is to use signals and slots. Since you need to know when the second window has been closed using the "back" button, create a custom signal for the second window that will be emitted whenever the function that is called by the button is closed.
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout(central)
self.testButton = QtWidgets.QPushButton('Test A')
self.closeButton = QtWidgets.QPushButton('Close')
layout.addWidget(self.testButton)
layout.addWidget(self.closeButton)
self.setCentralWidget(central)
self.testButton.clicked.connect(self.launchWindow)
self.closeButton.clicked.connect(self.close)
def launchWindow(self):
self.test = TestA_MainWindow()
self.test.backSignal.connect(self.show)
self.hide()
self.test.show()
class TestA_MainWindow(QtWidgets.QWidget):
backSignal = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
self.closeButton = QtWidgets.QPushButton('Close')
self.backButton = QtWidgets.QPushButton('Back')
layout.addWidget(self.closeButton)
layout.addWidget(self.backButton)
self.closeButton.clicked.connect(self.close)
self.backButton.clicked.connect(self.goBack)
def goBack(self):
self.close()
self.backSignal.emit()
def GUI_Window():
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
if __name__ == '__main__':
GUI_Window()
Notes:
I removed the GUI_Window class and made a function, as using a class for that is not really useful;
you should always prefer layout managers instead of setting manual geometries;
widgets should not be added to a QMainWindow as direct children, and a central widget should always be used (see the creation and use of central in the example); read more about it in the documentation;
only classes and constants should be capitalized, while variables, attributes and functions should always have names starting with a lowercase letter;

How to emit custom Events to the Event Loop in PyQt

I am trying to emit custom events in PyQt. One widget would emit and another would listen to events, but the two widgets would not need to be related.
In JavaScript, I would achieve this by doing
// Component 1
document.addEventListener('Hello', () => console.log('Got it'))
// Component 2
document.dispatchEvent(new Event("Hello"))
Edit: I know about signals and slots, but only know how to use them between parent and child. How would I this mechanism (or other mechanism) between arbitrary unrelated widgets?
In PyQt the following instruction:
document.addEventListener('Hello', () => console.log('Got it'))
is equivalent
document.hello_signal.connect(lambda: print('Got it'))
In a similar way:
document.dispatchEvent(new Event("Hello"))
is equivalent
document.hello_signal.emit()
But the big difference is the scope of the "document" object, since the connection is between a global element. But in PyQt that element does not exist.
One way to emulate the behavior that you point out is by creating a global object:
globalobject.py
from PyQt5 import QtCore
import functools
#functools.lru_cache()
class GlobalObject(QtCore.QObject):
def __init__(self):
super().__init__()
self._events = {}
def addEventListener(self, name, func):
if name not in self._events:
self._events[name] = [func]
else:
self._events[name].append(func)
def dispatchEvent(self, name):
functions = self._events.get(name, [])
for func in functions:
QtCore.QTimer.singleShot(0, func)
main.py
from PyQt5 import QtCore, QtWidgets
from globalobject import GlobalObject
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
button = QtWidgets.QPushButton(text="Press me", clicked=self.on_clicked)
self.setCentralWidget(button)
#QtCore.pyqtSlot()
def on_clicked(self):
GlobalObject().dispatchEvent("hello")
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
GlobalObject().addEventListener("hello", self.foo)
self._label = QtWidgets.QLabel()
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self._label)
#QtCore.pyqtSlot()
def foo(self):
self._label.setText("foo")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w1 = MainWindow()
w2 = Widget()
w1.show()
w2.show()
sys.exit(app.exec_())

PySide2 Signal cannot receive QObject parameter

In PySide2 I have a custom defined object:
class ResizeEvent(QObject):
def __init__(self, qresizeevent=None):
...
I want to a child widget emit a signal when it is resized and the parent widget receives and handles this event. So I defined a Signal in the child widget, a Slot in the parent widget, and connect them:
class ChildWidget(QWidget):
resized = Signal(ResizeEvent)
class ParentWidget(QWidget):
#Slot(ResizeEvent)
def onResized(self, event):
print("onResized", event)
...
...
def __init__(self):
...
self.child.resized.connect(self.onResized)
So that each time the child widget is resized, the parent widget will receive a signal and print out a string "onResized" followed by the custom event object. But the printed event object is None, and of course the following code goes into error. If I pass some int parameters instead of a ResizeEvent parameter into the signal, I got the correct parameters. So do a QtCore.QResizeEvent parameter.
It seems that my custom defined object cannot be successfully passed through the signal-slot connection. Why is it happening? Have I missed anything?
A simplified reproducible example is presented here:
import sys
from PySide2.QtCore import QObject, Signal, Slot
from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton
class ResizeEvent(QObject):
def __init__(self, qresizeevent=None):
self.size = qresizeevent.size()
self.oldSize = qresizeevent.oldSize()
class ChildWidget(QWidget):
resized = Signal(ResizeEvent)
def resizeEvent(self, event):
super().resizeEvent(event)
self.resized.emit(ResizeEvent(event))
class ParentWidget(QWidget):
def __init__(self):
super().__init__()
self.child = ChildWidget()
self.child.setFixedSize(200,50)
self.button = QPushButton("Increase Size")
self.layout = QVBoxLayout()
self.layout.addWidget(self.child)
self.layout.addWidget(self.button)
self.setLayout(self.layout)
self.child.resized.connect(self.onResized)
self.button.clicked.connect(self.onClicked)
#Slot(bool)
def onClicked(self, checked):
self.child.setFixedSize(self.child.width() + 10, self.child.height() + 10)
#Slot(ResizeEvent)
def onResized(self, event):
print("onResized", event)
if __name__ == "__main__":
app = QApplication()
widget = ParentWidget()
widget.show()
sys.exit(app.exec_())
When you inherit you must call the constructor of the class that you inherit:
class ResizeEvent(QObject):
def __init__(self, qresizeevent=None):
super(ResizeEvent, self).__init__() # <----
self.size = qresizeevent.size()
self.oldSize = qresizeevent.oldSize()

Send and receive signals from another class pyqt

I am needing a way to receive signals sent by a Class to another class.
I have 2 classes:
In my first class I have a function that emits a signal called 'asignal'
In my second class I call the first class function, and it emits a signal. but I can't connect the first class signal to my pushbutton. How I can do that?
I get this error: AttributeError: 'QPushButton' object has no attribute 'asignal'
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import *
import sys
class Signals(QObject):
asignal = pyqtSignal(str)
def __init__(self):
super(Signals, self).__init__()
self.do_something()
def do_something(self):
self.asignal.emit('Hi, im a signal')
class Main(QWidget):
def __init__(self):
super(Main, self).__init__()
self.setGeometry(300, 250, 400, 300)
self.show()
self.coso()
def coso(self):
btn = QPushButton('click me')
btn.asignal.connect(lambda sig: print("Signal recieved" + sig))
s = Signals()
s.do_something()
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Main()
app.exec_()
I want a way to receive the signal emitted from my 'Signals' class to my 'Main' class. And then, in my main class, connect the signal to a Widget.
QPushButton is not a Signals object, so thats why you get the error.
s = Signals()
s.asignal.connect(lambda sig: print("Signal recieved" + sig))
s.do_something()

how to emit signal from a non PyQt class?

i'm programming an application in python using twisted and PyQt . the problem that i'm facing is that when a function in my twisted code is executed i have to print a line in the GUI, i'm trying to achieve this by emiting a signal (Non PyQt class). This does not seem to work, i have a doubt that the twisted event loop is screwing things for PyQt. Because the closeEvent signal is not being trapped by the program.
Here's the code snippet:
from PyQt4 import QtGui, QtCore
import sys
from twisted.internet.protocol import Factory, Protocol
from twisted.protocols import amp
import qt4reactor
class register_procedure(amp.Command):
arguments = [('MAC',amp.String()),
('IP',amp.String()),
('Computer_Name',amp.String()),
('OS',amp.String())
]
response = [('req_status', amp.String()),
('ALIGN_FUNCTION', amp.String()),
('ALIGN_Confirmation', amp.Integer()),
('Callback_offset',amp.Integer())
]
class Ui_MainWindow(QtGui.QMainWindow):
def __init__(self,reactor, parent=None):
super(Ui_MainWindow,self).__init__(parent)
self.reactor=reactor
self.pf = Factory()
self.pf.protocol = Protocol
self.reactor.listenTCP(3610, self.pf) # listen on port 1234
def setupUi(self,MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(903, 677)
self.centralwidget = QtGui.QWidget(MainWindow)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
self.centralwidget.setSizePolicy(sizePolicy)
self.create_item()
self.retranslateUi(MainWindow)
self.connect(self, QtCore.SIGNAL('triggered()'), self.closeEvent)
QtCore.QObject.connect(self,QtCore.SIGNAL('registered()'),self.registered)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
self.pushButton_4.setText(_translate("MainWindow", "Delete System ", None))
self.pushButton.setText(_translate("MainWindow", "Add System", None))
self.label_2.setText(_translate("MainWindow", "SYSTEM STATUS", None))
self.label.setText(_translate("MainWindow", "Monitoring Output", None))
def registered(self):# this function is not being triggered
print "check"
self.textbrowser.append()
def closeEvent(self, event):#neither is this being triggered
print "asdf"
self.rector.stop()
MainWindow.close()
event.accept()
class Protocol(amp.AMP):
#register_procedure.responder
def register_procedure(self,MAC,IP,Computer_Name,OS):
self.bridge_conn=bridge()
cursor_device.execute("""select * FROM devices where MAC = ?;""",[(MAC)])
exists_val=cursor_device.fetchone()
cursor_device.fetchone()
print "register"
if not exists_val== "":
cursor_device.execute("""update devices set IP= ? , Computer_name= ? , OS = ? where MAC= ?;""",[IP,Computer_Name,OS,MAC])
QtCore.QObject.emit( QtCore.SIGNAL('registered')) # <--emits signal
return {'req_status': "done" ,'ALIGN_FUNCTION':'none','ALIGN_Confirmation':0,'Callback_offset':call_offset(1)}
else:
cursor_device.execute("""INSERT INTO devices(Mac,Ip,Computer_name,Os) values (?,?,?,?);""",[MAC,IP,Computer_Name,OS])
QtCore.QObject.emit( QtCore.SIGNAL('registered'))#<--emits signal
return {'req_status': "done" ,'ALIGN_FUNCTION':'main_loop()','ALIGN_Confirmation':0,'Callback_offset':0}
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
try:
import qt4reactor
except ImportError:
from twisted.internet import qt4reactor
qt4reactor.install()
from twisted.internet import reactor
MainWindow = QtGui.QMainWindow() # <-- Instantiate QMainWindow object.
ui = Ui_MainWindow(reactor)
ui.setupUi(MainWindow)
MainWindow.show()
reactor.run()
This is what I use in my one code for sending signals from QGraphicsItems (because they are not derived from QObject and cannot send/receive signals by default). It's basically a simplified version of Radio-'s answer.
from PyQt4 import QtGui as QG
from PyQt4 import QtCore as QC
class SenderObject(QC.QObject):
something_happened = QC.pyqtSignal()
SenderObject is a tiny class derived from QObject where you can put all the signals you need to emit. In this case only one is defined.
class SnapROIItem(QG.QGraphicsRectItem):
def __init__(self, parent = None):
super(SnapROIItem, self).__init__(parent)
self.sender = SenderObject()
def do_something_and_emit(self):
...
self.sender.something_happened.emit()
In the non-QObject class you add a SenderObject as a sender variable. Anywhere where the non-QObject class is used you can connect the signal from the sender to anything you need.
class ROIManager(QC.QObject):
def add_snaproi(self, snaproi):
snaproi.sender.something_happened.connect(...)
UPDATE
The full code is this and should print out "Something happened...":
from PyQt4 import QtGui as QG
from PyQt4 import QtCore as QC
class SenderObject(QC.QObject):
something_happened = QC.pyqtSignal()
class SnapROIItem(QG.QGraphicsItem):
def __init__(self, parent = None):
super(SnapROIItem, self).__init__(parent)
self.sender = SenderObject()
def do_something_and_emit(self):
self.sender.something_happened.emit()
class ROIManager(QC.QObject):
def __init__(self, parent=None):
super(ROIManager,self).__init__(parent)
def add_snaproi(self, snaproi):
snaproi.sender.something_happened.connect(self.new_roi)
def new_roi(self):
print 'Something happened in ROI!'
if __name__=="__main__":)
roimanager = ROIManager()
snaproi = SnapROIItem()
roimanager.add_snaproi(snaproi)
snaproi.do_something_and_emit()
UPDATE 2
Instead of
QtCore.QObject.connect(self,QtCore.SIGNAL('registered()'),self.registered)
you should have:
protocol.sender.registered.connect(self.registered)
This means you also need to get hold of the protocol instance in self.pf (by the way, do you import Protocol and then define it yourself as well?)
In the Protocol class instead of
QtCore.QObject.emit( QtCore.SIGNAL('registered')
you need to, first, instantiate a SenderObject in the Protocol.
class Protocol(amp.AMP):
def __init__( self, *args, **kw ):
super(Protocol, self).__init__(*args, **kw)
self.sender = SenderObject()
and then, in register_procedure emit the signal through sender:
self.sender.registered.emit()
For all of this to work you'll have to have defined SenderObject as:
class SenderObject(QC.QObject):
registered = QC.pyqtSignal()
This is an old post, but it helped me. Here is my version. One item that is not a QObject signaling two other non QObject's to run their methods.
from PyQt4 import QtGui, QtCore
class Signal(object):
class Emitter(QtCore.QObject):
registered = QtCore.pyqtSignal()
def __init__(self):
super(Signal.Emitter, self).__init__()
def __init__(self):
self.emitter = Signal.Emitter()
def register(self):
self.emitter.registered.emit()
def connect(self, signal, slot):
signal.emitter.registered.connect(slot)
class item(object):
def __init__(self, name):
self.name = name
self.signal = Signal()
def something(self):
print self.name, ' says something'
>>> itemA = item('a')
>>> itemB = item('b')
>>> itemC = item('c')
>>> itemA.signal.connect(itemA.signal, itemB.something)
>>> itemA.signal.connect(itemA.signal, itemC.something)
>>> itemA.signal.register()
b says something
c says something
The two basic problems are that:
1) Something has to know the sender and receiver of the signals
Consider a more frequent case in Qt where you may have multiple buttons, each with a 'clicked' signal. Slots need to know which button was clicked, so catching a generic signal does not make much sense.
and 2) The signals have to originate from a QObject.
Having said that, I'm not sure what the canonical implementation is. Here is one way to do it, using the idea of a Bridge that you had in one of your earlier posts, and a special Emitter class inside of Protocol. Running this code will simply print 'Working it' when protocol.register() is called.
from PyQt4 import QtGui, QtCore
import sys
class Ui_MainWindow(QtGui.QMainWindow):
def __init__(self):
super(Ui_MainWindow, self).__init__()
def work(self):
print "Working it"
class Protocol(object):
class Emitter(QtCore.QObject):
registered = QtCore.pyqtSignal()
def __init__(self):
super(Protocol.Emitter, self).__init__()
def __init__(self):
self.emitter = Protocol.Emitter()
def register(self):
self.emitter.registered.emit()
class Bridge(QtCore.QObject):
def __init__(self, gui, protocol):
super(Bridge, self).__init__()
self.gui = gui
self.protocol = protocol
def bridge(self):
self.protocol.emitter.registered.connect(self.gui.work)
app = QtGui.QApplication(sys.argv)
gui = Ui_MainWindow()
protocol = Protocol()
bridge = Bridge(gui, protocol)
bridge.bridge()
#protocol.register() #uncomment to see 'Working it' printed to the console
I improved the solution of #Ryan Trowbridge a bit to deal with a generic type. I hoped to use Generic[T] but turned out too complicated.
class GenSignal:
def __init__(self,typ):
Emitter = type('Emitter', (QtCore.QObject,), {'signal': Signal(typ)})
self.emitter = Emitter()
def emit(self,*args,**kw):
self.emitter.signal.emit(*args,**kw)
def connect(self, slot):
self.emitter.signal.connect(slot)
To use, x=GenSignal(int) and continue as usual.

Categories

Resources