Signals and slots in PySide - defining slot in class - python

I'm new to Qt and PySide and try to get a feeling for signals and slots in PySide.
While following the documentation of Signals and Slots in PySide, I tried to transfer some code to be used in my class.
The following code creates a DoubleSpinBox. When changing the value I expect the value to be printed by both functions value_changed_func and value_changed_class, but only value_changed_func gets called.
from PySide import QtCore
from PySide import QtGui
#QtCore.Slot(float)
def value_changed_func(value):
print "Event func:"
print value
class MainController(QtCore.QObject):
def __init__(self, parent):
self._ui = QtGui.QDoubleSpinBox(parent)
self._ui.valueChanged.connect(self.value_changed_class)
self._ui.valueChanged.connect(value_changed_func)
#QtCore.Slot(float)
def value_changed_class(self, value):
print "Event class:"
print value
app = QtGui.QApplication([])
main_window = QtGui.QMainWindow()
MainController(main_window)
main_window.show()
app.exec_()
What am I doing wrong? How do I get value_changed_class to be called?

You didn't call the parent constructor:
def __init__(self, parent):
super(MainController, self).__init__(parent)
...

Related

update the progressBar value from emitted signal

getting the CPU percentage from psutil and display it on a progress bar.
i was able to display the value of the CPU percentage but it's not updating.
i have a file from QT designer called Progress i convert it using pyuic5 to py file and import it in the main file called cpuprogress.py, also i created a python file called sysinfo to get the value of CPU percentage and import it to the main file.
import sys
from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (QWidget, QApplication)
from progress import Ui_Form
import sysinfo
class MyTest(QWidget, Ui_Form):
def __init__(self, parent = None):
super(MyTest, self).__init__(parent)
self.setupUi(self)
self.threadclass = ThreadCLass()
self.threadclass.start()
self.threadclass.change_value.connect(self.updateProgressBar)
def updateProgressBar(self, val):
self.progressBar.setValue(val)
class ThreadCLass(QThread):
change_value = pyqtSignal(int)
def __init__(self, parent = None):
super(ThreadCLass, self).__init__(parent)
def run(self):
while 1:
val = int(sysinfo.getCPU())
self.change_value.emit(val)
a =QApplication(sys.argv)
window = QWidget()
app = MyTest()
app.setupUi(window)
app.show()
sys.exit(a.exec_())
enter image description here
tl;dr
The problem is that you called setupUi twice, and the second time it was using another widget as argument.
Why doesn't it work?
When you generate a file with pyuic, it actually creates a class, based on Python's object type. That class doesn't do anything on itself, is just an "interface" to load the elements of the ui in a "pythonic way".
If you try to open one of those files (which should never be edited!), you'll see something like this:
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
self.progressBar = QtWidgets.QProgressBar(Form)
self.progressBar.setProperty("value", 24)
# ...
def retranslateUi(self, Form):
# things related to the localization of the ui
The official guide on using Designer reports as a first example that actually does nothing, besides showing the window.
from PyQt5.QtWidgets import QApplication, QWidget
from ui_form import Ui_Form
# create an instance of the base class
window = QWidget()
# create an instance of the Ui "builder"
ui = Ui_Form()
# apply the ui to the base class created before
ui.setupUi(window)
The last step is the most important to understand: look carefully to what object the setupUi method belongs, and its argument, then go back to the contents of the file created with pyuic:
def setupUi(self, Form):
Form.setObjectName("Form")
self.progressBar = QtWidgets.QProgressBar(Form)
Form is the QWidget instance created before ("window"), while obviously self refers to the instance of Ui_Form; let's translate the variables with the name of the instancies they represent:
def setupUi(ui, window):
window.setObjectName("Form")
ui.progressBar = QtWidgets.QProgressBar(window)
In the example at the beginning it means that, while window is displayed with its children widgets, they are not attributes of the instance: there's no window.progressBar.
The second example in the documentation shows the "single inheritance" method, that allows the implementation of interactions between widgets (the "logic"). I'll use the class names as I did above:
from PyQt5.QtWidgets import QApplication, QWidget
from ui_form import Ui_Form
class MyWidget(QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.ui = Ui_Form()
self.ui.setupUi(self)
Now ui is an attribute of the window instance; let's "translate" setupUi once again, assuming that now "self" is the instance of MyWidget that is being created:
class Ui_Form(object):
def setupUi(window.ui, window):
window.setObjectName("Form")
window.ui.progressBar = QtWidgets.QProgressBar(window)
This means that now you can have access to the widgets from the window instance, but only through self.ui (as in, window.ui).
Now, let's see the multiple inheritance approach, which is very similar:
class MyWidget(QWidget, Ui_Form):
def __init__(self):
super(MyWidget, self).__init__()
self.setupUi(self)
In this case, MyWidget inherits from the methods and attributes of both Qwidget and Ui_Form. Let's translate it again (note the class):
class MyWidget(object):
def setupUi(window, window):
window.setObjectName("Form")
window.progressBar = QtWidgets.QProgressBar(window)
This approach makes all widgets as direct attributes of the instance (self.progressBar, etc), and I usually suggest it as it's more direct and simple (it often happens that you try to access a widget and forget that ui prefix, with this method it doesn't happen).
Now, finally, your problem.
window = QWidget()
app = MyTest()
setupUi is called in the __init__ of MyTest, which means that app has an attribute called progressBar, which at this point should be "visible" as soon as we call app.show().
You also created another widget, though (window), and used setupUi using that as parameter:
app.setupUi(window)
Let's translate it one last time (to avoid confusion, let's change the name of the "window" QWidget you created by mistake):
def setupUi(app, otherWindow):
otherWindow.setObjectName("Form")
app.progressBar = QtWidgets.QProgressBar(otherWindow)
As you can see, now you've "overwritten" the app.progressBar attribute, but that progress bar is actually a new one, and it is a child of window (which you never show).
The updateProgressBar function then will successfully modify the value of the progress bar, but not the one you see.
Finally, there's also another way of using ui files, and it is through the uic module, which can directly load ui files.
from PyQt5 import QtWidgets, uic
class MyTest(QtWidgets.QWidget):
def __init__(self, parent=None)
super().__init__(parent)
uic.loadUi('mywindow.ui', self)
This approach as a big advantage on the other: you don't have to create files with pyuic anymore, and it behaves exactly as the multiple inheritance method, so you can use self.progressBar as you did before.

How to release some resources automatically just before a widget is destroyed?

I would like to release some resources just before a widget is destroyed, but I don't want to do it manually(I have many widgets, each widget has its own resource). I hope the release function is automatically called when I close the whole GUI software, just like I can do it in each widget destruct function using c++. How can I do it?
EDIT:The main resource is QThread, whose event loop is started upon the widget constructed. I want to stop event loop before the widget is destroyed.
EDIT: As #mguijarr said, I connect widget destroyed signal to resource release function, but it seems doesn't work:
from PySide import QtGui, QtCore
import sys
class MyWidget(QtGui.QWidget):
def __init__(self, *args, **kwargs):
print 'init MyWidget'
super(MyWidget, self).__init__(*args, **kwargs)
self.destroyed.connect(self.onDestroyed)
def onDestroyed(self):
print 'onDestroyed invoked'
if __name__ == '__main__':
a = QtGui.QApplication(sys.argv)
w = MyWidget()
w.setWindowTitle('Test')
w.show()
a.exec_()
print 'app exit!'
when I start the app and then close it, the output is:
init MyWidget
app exit!
You can connect the destroyed signal from QObject base class of your
widgets to your own destructor-like function, there is a trick though:
the widget need to have the 'DeleteOnClose' attribute set:
from PyQt4 import QtGui, QtCore
import sys
class MyWidget(QtGui.QWidget):
def __init__(self, *args, **kwargs):
print 'init MyWidget'
super(MyWidget, self).__init__(*args, **kwargs)
###
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
self.destroyed.connect(self.onDestroyed)
###
def onDestroyed(self):
print 'onDestroyed invoked'
if __name__ == '__main__':
a = QtGui.QApplication(sys.argv)
w = MyWidget()
w.setWindowTitle('Test')
w.show()
a.exec_()
print 'app exit!
Output:
~% python /tmp/bla.py
init MyWidget
onDestroyed invoked
app exit!

PySide: QMetaObject.connectSlotsByName emits warnings "No matching signal..." but still works..?

In Qt Designer, I created a QDialog window and used pysideuic to compile that to a base class which contains a setupUi method initialising all GUI elements and which I extend to implement the functionality, as so:
class MyDialog(QtGui.QDialog, ui_file.Ui_main_dialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
ui_file.Ui_main_dialog.__init__(self)
self.setupUi(self)
This setupUi method has calls to QtCore.QObject.connect for the signal-slot connections I created in Qt Designer, where I also added new slots to the GUI. These slots are non-existent in the base class generated by pysideuic and I added them to the MyDialog class, e.g.
def on_list_selection_changed(self):
self.run_btn.setEnabled(len(self.modules_list.selectedIndexes()) > 0)
For this example, the slot is called on_list_selection_changed() (with empty parameter list) in Qt Designer.
On initialisation, MyDialog.__init__ calls Ui_main_dialog.setupUi, which eventually calls QtCore.QMetaObject.connectSlotsByName (the latter two with the MyDialog instance's self which is currently being created). This emits the following line on sys.stderr:
QMetaObject::connectSlotsByName: No matching signal for on_list_selection_changed()
Still, the signal behaves correctly and the slot is called when the connected modules_list.itemSelectionChanged() (modules_list is a QListWidget).
So here is my question: why do I receive this warning? What can I do so it doesn't appear, given that it seems to be irrelevant?
Edit: Since I didn't receive any answers in the last 5 months, I thought I give a complete example to make reproducing the problem easier.
This example differs from the question above in that it only uses a QLineEdit instance. Here is the code:
import sys
from PySide import QtCore, QtGui
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
self.lineEdit = QtGui.QLineEdit(Dialog)
self.lineEdit.setObjectName("lineEdit")
QtCore.QObject.connect(self.lineEdit, QtCore.SIGNAL("textChanged(QString)"), Dialog.on_lineEdit_changed)
QtCore.QMetaObject.connectSlotsByName(Dialog)
class MainWindow(QtGui.QMainWindow, Ui_Dialog):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
Ui_Dialog.__init__(self)
Ui_Dialog.setupUi(self, self)
#QtCore.Slot(unicode) # signal with no arguments
def on_lineEdit_changed(self, text):
print 'Changed to', text
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
Note that the code for the Ui_Dialog class is generated by the pysideuic from the Qt Designer's .ui file, but I shortened it a bit to better highlight the problem.
I have the same issue in C++. connectSlotsByName is hard-coded to output that message if it finds a slot it can't connect automatically, even though you don't need the automatic connection because you've done it explicitly yourself. You can use qInstallMsgHandler to suppress warnings in general or write the messages somewhere better but I don't think there's any way to tell connectSlotsByName that you don't care about this case in particular.
I'm late but in case someone else comes here for this same question.
PySide auto connects the signals, this is how it worked for me since I didn't want to be connecting manually.
Here I use python 3.9 and PySide2, but it is the same for PySide6 and also for other versions of python.
import sys
import typing
from PySide2.QtCore import QObject, SignalInstance, Signal, Slot
from PySide2.QtWidgets import QMainWindow, QApplication
from .ui_mainwindow import Ui_MainWindow # Don't forget to add your import
class MyQObject(QObject):
mySignal: SignalInstance = Signal()
otherSignal: SignalInstance = Signal(str)
def __init__(self, parent: typing.Optional[QObject] = None) -> None:
super().__init__(parent)
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
QMainWindow.__init__(self)
# Instantiate before self.setupUI
self.myQObject = MyQObject(self)
self.myQObject.setObjectName("syncProduct") # do not forget this
# Load UI from designer & init
self.setupUi(self)
#Slot()
def on_myQObject_mySignal(self):
print("Handle mySignal")
#Slot(str)
def on_myQObject_otherSignal(self, text: str):
print(f"Handle otherSignal {text}")
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

Open a GUI file from another file PyQT

I've created many GUI interfaces in PyQT using QT Designer, but now I'm trying to open an interface from another one, and I don't know how to do it..
Start.py is the file which run the GUI Interface Authentification_1 and Acceuil_start.py is the file which run the GUI interface Acceuil_2.py, now I want from Start.py to lunch Acceuil_start.py.
Do you have any idea about that ? Thank you.
Here's my code :
Start.py :
import sys
from PyQt4 import QtCore, QtGui
from Authentification_1 import Ui_Fenetre_auth
from Acceuil_2 import Ui_MainWindow #??? Acceuil_2.py is the file which I want to open
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_Fenetre_auth()
self.ui.setupUi(self)
def authentifier(val): #Slot method
self.Acceuil = Acceuil() #???
self.Acceuil.show() #???
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = StartQT4()
myapp.show()
sys.exit(app.exec_())
Acceuil_start.py
import sys
from PyQt4 import QtCore, QtGui
from Authentification_1 import Ui_Fenetre_auth
from Acceuil_2 import Ui_MainWindow
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = StartQT4()
myapp.show()
sys.exit(app.exec_())
First, you should to name your GUI classes so they have a different name, and not the generic one, so you could distinct them.
Why you would need to do that? Well - simply, because it makes sense - if every class is representing different type of dialog, so it is the different type - and it should be named differently. Some of the names are/may be: QMessageBox, AboutBox, AddUserDialog, and so on.
In Acceuil_start.py (you should rename class in other module, too).
import sys
from PyQt4 import QtCore, QtGui
from Authentification_1 import Ui_Fenetre_auth
from Acceuil_2 import Ui_MainWindow
class Acceuil(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = Acceuil()
myapp.show()
sys.exit(app.exec_())
in the parent class, when you want to create the window, you are close (but it should work in any case):
def authentifier(val): #Slot method
self.Acceuil = Acceuil(self) # You should always pass the parent to the child control
self.Acceuil.show() #???
About parent issue: If your widget/window is creating another widget, setting creator object to be parent is always a good idea (apart from some singular cases), and you should read this to see why is that so:
QObjects organize themselves in object trees. When you create a QObject with another object as parent, it's added to the parent's children() list, and is deleted when the parent is. It turns out that this approach fits the needs of GUI objects very well. For example, a QShortcut (keyboard shortcut) is a child of the relevant window, so when the user closes that window, the shorcut is deleted too.
Edit - Minimal Working Sample
To see what I am trying to tell you, I've built a simple example. You have two classes - MainWindow and
ChildWindow. Every class can work without other class by creating separate QApplication objects. But, if you import ChildWindow in MainWindow, you will create ChildWindow in slot connected to singleshot timer which will trigger in 5 seconds.
MainWindow.py:
import sys
from PyQt4 import QtCore, QtGui
from ChildWindow import ChildWindow
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
QtCore.QTimer.singleShot(5000, self.showChildWindow)
def showChildWindow(self):
self.child_win = ChildWindow(self)
self.child_win.show()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = MainWindow()
myapp.show()
sys.exit(app.exec_())
ChildWindow.py:
import sys
from PyQt4 import QtCore, QtGui
class ChildWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setWindowTitle("Child Window!")
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = ChildWindow()
myapp.show()
sys.exit(app.exec_())
To reference the other dialog from Start.py, you must prefix it by the module name, which in this case is Acceuil_start. As such, it is OK if there are duplicated function names in each module. So, you would have:
def authentifier(val): #Slot method
dlg = Acceuil_start.StartQT4()
dlg.exec_()
However, if you want these to run from the same process, keep in mind that you can't have two app objects. You would probably want to structure Acceuil_start.py to act like a dialog, rather than a main window. If these are two distinct main windows, you might find it easier to just invoke another Python interpreter with the Acceuil_start.py as a parameter.

Using Python PyQT4 slots and signals in Monkey Studio

I'm writing my first GUI application using PyQT4 and the Monkey Studio ide.
I've made a dialog (mainwindow.ui) with a button that sends the signal clicked() to the MainWindow's slot slot1()
This is the MainWindow code:
from PyQt4 import uic
(Ui_MainWindow, QMainWindow) = uic.loadUiType('mainwindow.ui')
class MainWindow (QMainWindow):
"""MainWindow inherits QMainWindow"""
def __init__ (self, parent = None):
QMainWindow.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
def __del__ (self):
self.ui = None
def slot1(self):
print "Test"
It does not work: AttributeError: 'MainWindow' object has no attribute 'slot1'
I've tried adding #pyqtSlot("") before def slot1(self), but I get this error:
NameError: name 'pyqtSlot' is not defined
I've also tried #QtCore.pyqtSignature("slot1()"), to no effect.
Turns out I also had to import from PyQt4.QtCore import *, which made me able to use #pyqtSlot().
Without the quotes, because that would throw another C++ error.

Categories