connecting pyqt4 signals in a pyqt4 qobject class - python

I've got two classes; one for my window and one for my controlling object
class window(baseClass, testForm):
scanStarted = QtCore.pyqtSignal(str)
def __init__(self,parent=None):
super(window, self).__init__(parent)
self.setupUi(self)
#other window setup
self._scanner.pushScan.clicked.connect(self._scanClicked)
def _scanClicked(self):
self.scanStarted.emit( self._scanner.getTextData() )
and my controlling object
class vis(QtCore.QObject):
def __init__(self):
self._oreList = []
self._w = window()
self._w.scanStarted.connect(self._scanOre)
def _scanOre(self, rawText):
print "main ->", rawText
When using the QtCore.QObject as my reference, this signal won't connect to the _scanOre. When I switch the reference to python 'object' it'll work fine. I've been trying to figure out why it won't connect using the QtCore.QObject type.
The signal will also connect just fine in the window class regardless.
I tried giving the _scanOre the #QtCore.pyqtSlot(str, name='scanGo') and adding the name parameter into the signal creation as well. I'm not sure what I'm missing here.

You forgot to initialize the QObject:
class vis(QtCore.QObject):
def __init__(self, parent=None):
super(vis, self).__init__(parent) # you are missing this line
# also the `parent` arg
self._oreList = []
self._w = window.window()
self._w.scanStarted.connect(self._scanOre)
def _scanOre(self, rawText):
print "main ->", rawText

Related

Proper implementation of access to ui elements for custom widgets in PySide or PyQT

I am building a program to view a video and do image processing on it. I want to know the proper way to give my individual classes access to the ui elements.
The way I implemented it now is the following:
I designed a GUI in QT Creator and save the video.ui file, then I generate the Ui_video.py file from it with pyside6-uic.
My main.py then looks like this:
import sys
from PySide6.QtWidgets import QMainWindow, QApplication
from PySide6.QtCore import QCoreApplication
from ui_video import Ui_Video
class VideoViewerMain(QMainWindow):
def __init__(self):
super().__init__()
self.ui : Ui_Video = None
def closeEvent(self, event):
self.ui.video_view.closeEvent(event)
return super().closeEvent(event)
class App(QApplication):
def __init__(self, *args, **kwargs):
super(App,self).__init__(*args, **kwargs)
self.window = VideoViewerMain()
self.ui = Ui_Video() #
self.window.ui = self.ui
self.ui.setupUi(self.window)
self.window.show()
if __name__ == "__main__":
# init application
app = App(sys.argv)
app.processEvents()
# execute qt main loop
sys.exit(app.exec())
Then I implemented my video previewer, which inherits from QOpenGLWidget as a way to display the video. (I guess this could be any widget type that supports painting)
The Qt hierarchy looks like this:
In the QT Creator I set the corresponding widget as a custom class. This means my class is instantiated when the main class calls setupUI.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ui_video import Ui_Video
class VideoPreview(QOpenGLWidget):
"""
widget to display camera feed and overlay droplet approximations from opencv
"""
def __init__(self, parent=None):
super().__init__(parent)
self.ui: Ui_Video = self.window().ui
self.reader = VideoReader(r"some/video/path")
# adjust the seekBar range with video length, this throws error
self.ui.seekBar.setMaximum(self.reader.number_of_frames - 1)
self.ui.seekBar.setMinimum(0)
self._first_show = True
def showEvent(self, event: QtGui.QShowEvent) -> None:
if self._first_show:
self._first_show = False
self.ui.seekBar.setMaximum(self.reader.number_of_frames - 1)
self.ui.seekBar.setMinimum(0)
return super().showEvent(event)
When I try to initialize the ui seekBar in the init function, it throws an error
File "src/VideoProcessor\videopreview.py", line 56, in __init__
self.ui.seekBar.setMaximum(self.reader.number_of_frames - 1)
AttributeError: 'Ui_Video' object has no attribute 'seekBar'
This is because my custom class is instantiated before the seekBar in the setupUI function which I cannot change. Currently I use the workaround with the showEvent.
So my question is: How would a proper implementation of a custom widget look like in this context?
Should I divide the functionality of the video controls from the widget entirely?
How can I ensure that the classes have access to the ui elements?
The main thing I see is your use of colons in assignment statements.
In python colons are mainly used to for setting dictionary literal values...
my_dict = {"my_key1": 1, "my_key2": 2}
ending loops/condition statements...
for index in range(12):
if index == 5:
print("FIVE")
and the occasional lambda when necessary...
sorted_my_dict = sorted(my_dict.items(), key=lambda item: item[1])
In your case I see at least 2 places where you are using colons in ways I've never personally seen in python.
self.ui : Ui_Video = None
# and
self.ui: Ui_Video = self.window().ui
If you need to set a value to None just use
self.ui = None
when setting it to an existing object from another widget either utilize the parent argument or just pass the object in as a different argument...
self.ui = parent.ui
# or
class VideoPreview(QOpenGLWidget):
"""
widget to display camera feed and overlay droplet approximations from opencv
"""
def __init__(self, ui_object, parent=None):
super().__init__(parent)
self.ui = ui_object.ui
[...]

The signal and slot within custom class including PyQt QWidget not working

I have some trouble customizing a class including a QPushButton and QLabel, and I just want to set the QPushButton checkable and define a slot to its toggled signal, in addition, the custom class inherents QObject.
The code is shown below,
import sys
from PyQt5 import QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import pyqtSlot, QObject
class CustomButton(QPushButton):
def __init__(self, label='', parent=None):
super().__init__(label, parent)
self.setCheckable(True)
self.toggled.connect(self.update)
def update(self, state):
if state:
self.setStyleSheet('background-color: green')
else:
self.setStyleSheet('background-color: red')
class ButtonCtrl(QObject):
def __init__(self, parent=None, label=''):
super().__init__()
if isinstance(parent, QLayout):
col = QVBoxLayout()
parent.addLayout(col)
else:
col = QVBoxLayout(parent)
self.text = ['ON', 'OFF']
self.label = QLabel(label)
self.button = QPushButton('ON')
# self.button = CustomButton('ON')
col.addWidget(self.label)
col.addWidget(self.button)
self.button.setCheckable(True)
self.button.setChecked(True)
self.button.toggled.connect(self.update)
self.update(True)
self.label.setFont(QFont('Microsoft YaHei', 14))
self.button.setFont(QFont('Microsoft YaHei', 12, True))
self.button.toggle()
# #pyqtSlot(bool)
def update(self, state):
if state:
self.button.setText(self.text[0])
self.button.setStyleSheet('background-color: green')
else:
self.button.setText(self.text[-1])
self.button.setStyleSheet('background-color: red')
class Window(QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
# set the layout
layout = QVBoxLayout(self)
but = ButtonCtrl(layout, "Test")
#self.but = ButtonCtrl(layout, "Test")
btn = CustomButton()
layout.addWidget(btn)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle('Fusion')
main = Window()
main.show()
sys.exit(app.exec_())
I have customized two buttons, named CustomButton(QPushButton) and ButtonCtrl(QObject), and I have tested the slot in the main window, however the background update slot works for CustomButton(QPushbutton) and does not work for ButtonCtrl(QObject), the slot function is not even invoked.
However, if I change the button member of ButtonCtrl(QObject) from QPushButton into my CustomButton(QPushButton), the it will work well in the main window. Furthermore, if the but in main window becomes a member of the main window class by setting self.but=ButtonCtrl(layout, "Test"), it will work as well.
I didn't find direct answer to it in Qt documentation which explains that
Signals are emitted by an object when its internal state has changed in some way that might be interesting to the object's client or owner. Signals are public access functions and can be emitted from anywhere, but we recommend to only emit them from the class that defines the signal and its subclasses.
I am not sure if the lifetime of the but causing this effect, hope to get an answer, thank you.
The problem is simple: The ButtonCtrl class object has a local scope so it will be destroyed, and why doesn't the same happen with the CustomButton class object? Well, because the ownership of a QWidget has its parent, and the parent of that button is the window, instead the ButtonCtrl object does not have it. In this case the solution is to extend the life cycle of the variable, and in the case of a QObject there are several options:
make the class member variable,
Place it in a container with a longer life cycle, or
establish a QObject parent with a longer life cycle.
Using the third alternative is just to change to:
class ButtonCtrl(QObject):
def __init__(self, parent=None, label=''):
super().__init__(parent)
# ...
The first option is the one commented on in your code:
self.but = ButtonCtrl(layout, "Test")
and the second is similar:
self.container = list()
but = ButtonCtrl(layout, "Test")
self.container.append(but)

AttributeError: "Dialog" object has no attribute 'setupUi'

I made two forms on pyqt5 - qt-designer. One is the Main Form and the second is a Dialog which I will use for user input. I converted both to py code.
First of all I should inform you that I do not modify the converted Ui.py files. I prefer to have an extra "Main" file where I set each modification. I do this so that I won't have to make the extra changes each time I modify with the Ui files.
So, I added a second class on my Main file and tried to call it from my MainForm Class through a menu item. Here is some example code:
class MainForm(QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
self.ui = Ui_MainForm()
self.ui.setupUi(self)
self.ui.actionMenu1.triggered.connect(self.open_my_dialog)
def open_my_dialog(self):
my_dialog = QDialog()
my_dialog.ui = MyDialog()
my_dialog.ui.setupUi(my_dialog)
# MainForm.hide(self)
my_dialog.exec_()
my_dialog.show()
class MyDialog(QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.ui = Ui_MyDialog()
self.ui.setupUi(self)
self.ui.pushButton_cancel.clicked.connect(self.cancel_dialog)
def cancel_dialog(self):
print("Closing Dialog Window...")
sys.exit()
When I run this and click the respective menu button I get the following error:
AttributeError: 'MyDialog' object has no attribute 'setupUi'
the Error is at this line:
self.ui.setupUi(self) # this is under MyDialog Class
I can get the code working if I reference it to the external (Ui_MyDialog) file directly without using the second class here. But as I stated at the beginning I want to control this from within this file, make the modifications directly here so that I won't have the keep track of the modifications to the Ui files in the future.
Given that the error occurs at this line
self.ui.setupUi(self)
and you assigned an Ui_MyDialog instance to self.ui, before
self.ui = Ui_MyDialog()
the error message should be mentioning class Ui_MyDialog and not MyDialog.
So, either you misreported the error message or the error does not occur 'under MyDialog Class'.
I also would point out: QWidget and derived classes have no setupUi method, themselves. Such method belongs to Ui_* classes, generated by uic, and is typically called from the constructor of a widget that inherits from a Ui_* class.
So, if you want to call setupUi on MyDialog instances, MyDialog must inherit from Ui_MyDialog, in the first place.
And you do call it in open_my_dialog:
my_dialog.ui = MyDialog()
my_dialog.ui.setupUi(my_dialog)
Finally I got it to work. But before I provide the answer, I would like to thank
#p-a-o-l-o and #LoïcG.. the latter helping me out all the way. Thanks!
The code worked when I changed it to the following:
class MainForm(QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
self.ui = Ui_MainForm()
self.ui.setupUi(self)
self.ui.actionMenu1.triggered.connect(self.open_my_dialog)
def open_my_dialog(self):
my_dialog = MyDialog()
# my_dialog.show() <-- seems this line is not necessary either
my_dialog.exec_() # this alone was enough for the code to work
class MyDialog(QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.ui = Ui_MyDialog()
self.ui.setupUi(self)
self.ui.pushButton_cancel.clicked.connect(self.cancel_dialog)
def cancel_dialog(self):
print("Closing Dialog Window...")
self.close() # not important but corrected this also.
I hope this helps!
Edit: I corrected some lines and made the answer more simple thanks to
#LoïcG. !

How to catch a Signal from QMainWindow class by another thread on PySide?

I have a MainWindow class which have a Gui application running on it and i want that every time i click on a button from my application a signal is emitted and caught by another thread. There is my example code (sorry for not posting my real code but it is real big now):
from PySide.QtGui import *
from PySide.QtCore import *
import sys
import mainGui #Gui file
class MainWindow(QMainWindow, mainGui.Ui_MainWindow):
mySignal = Signal()
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.newThread = workThread()
self.newThread.start()
#myButton is part of Gui application
self.myButton.clicked.connect(self.myfunction)
def myfunction(self):
self.mySignal.emit()
(...) #Other functions and methods
class workThread(QThread):
def __init__(self, parent=None):
super(workThread, self).__init__(parent)
#The problem:
MainWindow.mySignal.connect(self.printMessage)
def run(self):
(...)
def printMessage(self):
print("Signal Recived")
(...)
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.show()
app.exec_()
if __name__=="__main__":
main()
... and i get the following error:
MainWindow.mySignal.connect(self.printMessage)
AttributeError: 'PySide.QtCore.Signal' object has no attribute 'connect'
There is any ideia how can i solve this?
Thanks in advance!
Signals are just like methods - they must be bound to instances. They won't work correctly if you try to access them directly via the class.
One way to fix the example is to pass the instance of MainWindow in as the parent of the thread, like so:
self.newThread = workThread(self)
...
parent.mySignal.connect(self.printMessage)

Using Signals for communicating between classes

I want to use signals for communicating between my view and my application controller. I have following approach but since I'm beginner in PyQt I don't know if that is the right one. Can anyone tell me If I am on the right path or are there better solutions?
EDIT: I have changed the example to a fully working example.
import sys
from PyQt4 import QtGui, QtCore
class View(QtGui.QMainWindow):
sigFooChanged = QtCore.pyqtSignal()
sigBarChanged = QtCore.pyqtSignal()
def __init__(self):
QtGui.QMainWindow.__init__(self)
central_widget = QtGui.QWidget()
central_layout = QtGui.QHBoxLayout()
self.__cbFoo = QtGui.QComboBox()
self.__cbBar = QtGui.QComboBox()
self.__cbFoo.currentIndexChanged[str].connect(lambda x: self.sigFooChanged.emit())
self.__cbBar.currentIndexChanged[str].connect(lambda x: self.sigBarChanged.emit())
central_layout.addWidget(QtGui.QLabel("Foo:"))
central_layout.addWidget(self.__cbFoo)
central_layout.addWidget(QtGui.QLabel("Bar:"))
central_layout.addWidget(self.__cbBar)
central_widget.setLayout(central_layout)
self.setCentralWidget(central_widget)
def setFooModel(self, model):
self.__cbFoo.setModel(model)
def setBarModel(self, model):
self.__cbBar.setModel(model)
class Controller:
def __init__(self, view):
self.__view = view
# Connect all signals from view with according handlers
self.__view.sigFooChanged.connect(self.handleFooChanged)
self.__view.sigBarChanged.connect(self.handleBarChanged)
self.__fooModel = QtGui.QStringListModel(["Foo1", "Foo2", "Foo3"])
self.__barModel = QtGui.QStringListModel(["Bar1", "Bar2", "Bar3"])
self.__view.setFooModel(self.__fooModel)
self.__view.setBarModel(self.__barModel)
def handleFooChanged(self):
print("Foo Changed")
def handleBarChanged(self):
print("Bar Changed")
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
view = View()
controller = Controller(view)
view.show()
sys.exit(app.exec_())
Personally, I don't create a separate generic controller class like that. It could be my own preference, but I tend to consider the actual QWidget class my controller, and the view is usually the GUI-only definitions that I generate from QtDesigner (Ui_Dialog for example), or create manually. And I make all my connections in the relevant QWidget.
Now on to your code, I don't know if you are just considering this snippet a general pseudocode example of the direction you are taking...but it has errors... I would normally suggest posting working code so people don't get confused as to whether you are having errors because of it, or just asking if its generally a correct direction to laying out code.
You are forgetting to call __init__() on the QMainWindow superclass.
I'm not sure what controller.show() would do (fail as of right now) because I don't see an example of how you intend to forward that show() command to your main window object? Again I don't really see why its even necessary to have that separate class.
Here is how I would see a more realistic example, again considering the QWidget classes themselves to be the controllers:
View
## mainUI.py ##
from PyQt4 import QtCore, QtGui
class Ui_MyWidget(object):
def setupUi(self, obj):
obj.layout = QtGui.QVBoxLayout(obj)
obj.cbFoo = QtGui.QComboBox()
obj.cbBar = QtGui.QComboBox()
obj.layout.addWidget(obj.cbFoo)
obj.layout.addWidget(obj.cbBar)
Non-Gui Library Module (Controller)
## nonGuiModule.py ##
class LibModule(object):
def handleBarChanged(self, *args):
print("Bar Changed: %s" % args)
Controller (any entry point)
## main.py ##
import sys
from PyQt4 import QtCore, QtGui
from mainUI import Ui_MyWidget
from nonGuiModule import LibModule
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.resize(640,480)
self._lib = LibModule()
self.myWidget = MyWidget(self)
self.setCentralWidget(self.myWidget)
self.myWidget.sigFooChanged.connect(self.handleFooChanged)
self.myWidget.sigBarChanged.connect(self._lib.handleBarChanged)
def handleFooChanged(self, *args):
print("Foo Changed: %s" % args)
class MyWidget(QtGui.QFrame, Ui_MyWidget):
sigFooChanged = QtCore.pyqtSignal(str)
sigBarChanged = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
# this is where you set up from the view
self.setupUi(self)
self.cbFoo.addItems(['Foo1', 'Foo2'])
self.cbBar.addItems(['Bar1', 'Bar2'])
self.layout.addWidget(self.cbFoo)
self.layout.addWidget(self.cbBar)
# going to forward private signals to public signals
self.cbFoo.currentIndexChanged[str].connect(self.sigFooChanged)
self.cbBar.currentIndexChanged[str].connect(self.sigBarChanged)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv[1:])
view = Main()
view.show()
sys.exit(app.exec_())

Categories

Resources