Inheriting from custom PyQt5 QWidget does not call parent method - python

I am coding a little application in PyQt5 and came across a problem illustrated at hand of the following minimal working example:
My custom class 'A' inherits from the QWidget class, calls two setup methods (one for widgets and one for layouts) and adds the widgets from the setup_widgets method ('Click A' Button) to the Layout called 'frame'. This works fine and creating a QApplication as well as a QMainWindow and adding such a class A widget to it will display it properly.
However in the next step I design a class 'B' which is supposed to inherit from class 'A'. Hence I call the init method of 'A' within the init method of 'B'. To my understanding this will go through A.init() including the A.setup_widgets() as well as A.setup_layout(), which adds the 'Click A' Button as well as a Layout called 'frame' to the object.
After a little of debugging though I noticed that the call of setup_layout that comes from the A.init() does actually call the B.setup_layout since the 'self' argument of the inheritance call seems to be an object of type 'B'. Therefore an Error is raised since for the B object no Layout called 'frame' was ever created.
A workaround would be to add A.setup_widgets(self) as a first line to the B.setup_widgets method, equivalently adding A.setup_layout(self) to B.setup_layout method. This approach is also shown in the source code, but commented with a #. However this results in an attempt to set a QLayout twice and thus in a Warning/Error:
QWidget::setLayout: Attempting to set QLayout "" on B "", which already has a layout.
What is the proper way to deal with this issue of inheritance?
If it helps: I never plan to use class A as an actual object, still would like it to be a solo standing functioning class since I will derive many different subclasses from it.
Thanks and cheers,
Paul
from PyQt5.QtWidgets import *
class A(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setup_widgets()
self.setup_layout()
def setup_widgets(self):
self.button_a = QPushButton('Click A')
def setup_layout(self):
self.frame = QHBoxLayout()
self.frame.addWidget(self.button_a)
self.setLayout(self.frame)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
win = QMainWindow()
wid = A()
win.setCentralWidget(wid)
win.show()
sys.exit(app.exec())
-------------------------------------------------------------------------
from PyQt5.QtWidgets import *
from class_a import A
class B(A):
def __init__(self):
A.__init__(self)
self.setup_widgets()
self.setup_layout()
def setup_widgets(self):
#A.setup_widgets(self)
self.button_b = QPushButton('Click B')
def setup_layout(self):
#A.setup_layout(self)
self.frame.addWidget(self.button_b)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
win = QMainWindow()
wid = B()
win.setCentralWidget(wid)
win.show()
sys.exit(app.exec())

You don't need call setup_widgets and setup_layout from your B. __init__ since they are already called by A.

Related

When changing screens with QStackedWidget, how to call a function in the changed screen?

I'm trying to call the init function of the screen I'm changing my screen index to
For an example, i have this code:
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from sys import argv as sysArgv
from sys import exit as sysExit
arialLarge = qtg.QFont("Arial", 18)
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# Current screen label;
mainWindowLabel = qtw.QLabel("This is the main window", self)
mainWindowLabel.setFont(arialLarge)
mainWindowLabel.move(20, 40)
# Button for going to the HelloWindow screen;
gotoHelloWindowButton = qtw.QPushButton("Go to hello window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()+1))
gotoHelloWindowButton.move(100, 100)
class HelloWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# EG: print hello world when I visit this page
print("hello world")
# Current screen label;
helloWindowLabel = qtw.QLabel("This is the hello window", self)
helloWindowLabel.setFont(arialLarge)
helloWindowLabel.move(20, 40)
# Button for going to the MainWindow screen;
gotoMainWindowButton = qtw.QPushButton("Go to main window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()-1))
gotoMainWindowButton.move(100, 100)
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = qtw.QStackedWidget()
appStack.addWidget(MainWindow())
appStack.setFixedSize(300, 300)
appStack.show()
appStack.addWidget(HelloWindow())
sysExit(app.exec())
If im visiting the HelloWindow from the MainWindow, how can i run the init function of the HelloWindow screen so I can run whatever code I want in there?
I need to be able to do this as on the app im working on as on the mainpage i have dynamically created buttons that all have functions parameters with different indexes to my server, and i need to be able to fetch the data from server based off the clicked button's data index so on the other page I can view the desired data.
The __init__ of a python class is what is called when an instance is created (using SomeClass()), so you should not try (or even think) to call it again, as it could create serious problems and bugs that are hard to track.
I strongly suggest you to read the documentation about classes in Python, as you cannot ignore that aspect in object oriented programming.
If you need to call something everytime the index is changed, then you should better subclass QStackedWidget and control everything from there.
A good solution is to create a standardized function that will be called everytime the page is presented, and ensure that the stack widget correctly calls it.
class FirstPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.nextButton = QtWidgets.QPushButton('Next')
self.doSomething()
def doSomething(self):
...
class SecondPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.prevButton = QtWidgets.QPushButton('Previous')
self.doSomething()
def doSomething(self):
...
class Stack(QtWidgets.QStackedWidget):
def __init__(self):
super().__init__(self)
self.first = FirstPage()
self.first.nextButton.clicked.connect(self.goNext)
self.addWidget(self.first)
self.second = SecondPage()
self.second.prevButton.clicked.connect(self.goPrev)
self.currentChanged.connect(self.initCurrent)
def goNext(self):
self.setCurrentIndex(1)
def goPrev(self):
self.setCurrentIndex(0)
def initCurrent()
if self.currentWidget():
self.currentWidget().doSomething()
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = Stack()
appStack.setFixedSize(300, 300)
appStack.show()
sysExit(app.exec())
Note that adding a QMainWindow to a parent is not a good idea, as Qt main windows are intended to be used as top level windows; also note that using fixed geometries (positions and sizes) is often considered bad practice, and you should use layout managers instead.

How to switch between two PyQt5 MainWindow widgets

I'm writing a program that has two different parts to it - let's call them sub1 and sub2. When I initially run my program, sub1 displays and I'm loading sub2 in the background but not displaying it. I have a menu action in sub1 that allows you to switch to sub2 and there is a menu action in sub2 that allows you to switch back to sub1. The problem I have is when trying to switch back from sub2 to sub1. Going from sub1 to sub2 works fine; sub1 gets hidden and sub2 is displayed. However, when trying to show sub1 again, sub2 doesn't get hidden. I'm new to PyQt as well as Python so I don't know all the intricacies yet. So, the method I'm using is just something I figured through trial and error and by no means needs to be this way. Simplified code below.
#mass module
class MASS(PyQt5.QtWidgets.QMainWindow, massUi.Ui_MainWindow):
def __init__(self):
super(MASS, self).__init__()
self.actionSwitchToCompEval.triggered.connect(self.switchToCompEval)
def switchToCompEval(self):
massForm = main.massForm
massForm.hide()
compForm = main.compForm
compForm.show()
def showMass(self):
main(1)
def main(initiate=None):
if initiate == None:
main.app = PyQt5.QtWidgets.QApplication(sys.argv)
main.massForm = MASS()
main.compForm = CompEval.CompEval()
main.massForm.show()
main.app.exec_()
elif initiate == 1:
main.massForm = MASS()
main.compForm = CompEval.CompEval()
main.compForm.hide()
main.massForm.show()
elif initiate == 2:
pass
if __name__ == '__main__':
main()
#comp module
class CompEval(PyQt5.QtWidgets.QMainWindow, compEvalUi.Ui_MainWindow):
def __init__(self):
super(CompEval, self).__init__()
self.actionSwitchToMASS.triggered.connect(self.switchToMass)
def switchToMass(self):
mass.MASS().showMass()
def main():
form = CompEval()
form.show()
In the switchToCompEval function, it seems to work fine to reference the main.massForm and main.compForm variables but when I try to go from sub2(comp) back to sub1(mass) I get an error that the function does not contain that variable, which seems odd to me. I am aware that several aspects of how I have this setup at the moment is odd and far from ideal, so any suggestions would be appreciated. Thanks.
So after much experimentation I determined the best solution to this problem is to combine the modules into one. IF you have multiple MainWindow widgets and you need to be able to switch back and forth between them, keep your classes that access the widgets all in the same module.
So I have my two widget classes:
import PyQt5
import sys
#Below are the modules that are auto-generated when using Qt Designer to make an interface
import compWidget as compEvalUi
import massWidget as massUi
class MASS(PyQt5.QtWidgets.QMainWindow, massUi.Ui_MainWindow
def __init__(self):
super(MASS, self).__init__()
#setupUi function is in the auto-generated module and contains all the attributes of the interface
self.setupUi(self)
#menuSwitch is the name of the button/menu item that would need to be clicked
#in order to switch windows.
#The example shown here is if the action was attached to a menu-dropdown item
#which is why 'triggered' is used as opposed to 'clicked' if it were attached to a button
self.menuSwitch.triggered.connect(self.switchToCompWidget)
def switchToCompWidget(self):
INITIALIZE.massForm.hide()
INITIALIZE.compForm.show()
class CompEval(PyQt5.QtWidgets.QMainWindow, compEvalUi.Ui_MainWindow):
def __init__(self):
super(CompEval, self).__init__()
self.setupUi(self)
self.menuSwitch.triggered.connect(self.switchToMassWidget)
def switchToMassWidget(self):
INITIALIZE.compForm.hide()
INITIALIZE.massForm.show()
class INITIALIZE:
def __init__(self):
app = PyQt5.QtWidgets.QApplication(sys.argv)
INITIALIZE.massForm = MASS()
INITIALIZE.massForm.show()
INITIALIZE.compForm = CompEval()
app.exec_()
def main():
program = INITIALIZE()
if __name__ =='__main__':
main()
You could use signals and slots to separate the logic instead of sharing a global parameters between classes.

2 PyQT Interface in 1 Python file

Is it possible to have 2 PyQT UI in 1 .py file?
Example code:
Class a(object):
*insert code for PyQT First UI
Class b(QMainWindow):
*insert code for PyQT Second UI
Class a runs first, but how do i switch from Class a to Class b and vice versa?
One file can contain as many ui classes as you want. Just by declaring the class does not run the ui. You can display the ui in several different ways.
To display a second QWidget from your class a, you can call it from a method like this:
def show_b(self):
self.dialog = b()
self.dialog.show()
This works if class b is a QWidget. If it is a QDialog, it can be displayed as follows:
def show_b(self):
dialog = b()
dialog.show() # this line makes the dialog non-modal, it is optional
dialog.exec_() # displays the dialog
If you want to have two windows and be able to switch to either one of them, you just have to declare them in your main file:
def main_Window_A(QMainWindow):
# code for window A
def main_window_B(QMainWindow):
# code for window B
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window_A = main_Window_A()
window_A.show()
window_B = main_Window_B()
window_B.show()
sys.exit(app.exec())

Changed to options listed in PyQt4 combo_boxes defined in a different class not reflected

I am working on a small GUI project using PyQt4. I have defined one class (inside a separate file) defining the basic functionality of combo_boxes that I have to use and another class to use the functionality for all the combo_boxes.
The code looks something like
class core:
def __init__(self, default_value, window_name):
self.combo_box = QtGui.QComboBox(window_name)
self.combo_box.addItem(default_value)
self.combo_box.addItem("some other value")
self.combo_box.addItem("a third value")
self.combo_box.activated[str].connect(self.set_text)
self.text = default_value
def set_text(self, text):
print text
The main class is something like:
from file import *
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(200, 100, 820, 700)
combo_box_one = core("first", self)
combo_box_two = core("second", self)
#some other methods follow defining the geometry for each combo_box and other functions
def main():
app = QtGui.QApplication(sys.argv)
gui = Window()
sys.exit(app.exec_())
main()
The GUI is working as expected. All the combo_boxes appear as per the defined geometry. However, when on selecting different options, nothing seems to happen. Ideally, I would expect the text on the option to be printed. In fact, when I return the combo_box object to the main class and set it connections there, the change in options is reflected. But when the same thing is done in the coreclass, the changes are not reflected as printed text. Is it a scope related thing? Please help me understand what's happening.
Slots can only be implemented in classes that inherit from QObject, a simple solution is that the core class inherits from QComboBox, since QComboBox inherits from QObject.
class core(QtGui.QComboBox):
def __init__(self, default_value, window_name):
QtGui.QComboBox.__init__(self, window_name)
self.addItem(default_value)
self.addItem("some other value")
self.addItem("a third value")
self.activated[str].connect(self.set_text)
def set_text(self, text):
print(text)

Python 3 pyQt4 updating GUI with variables from multiple modules/classes

I've written a large program with nested classes/threads and multiple modules.
I would now like to add a simple GUI and some Labels to display some variables.
However, the variables are scattered throughout the modules and classes.
I'm looking for a way to update these variables into the GUI without altering
the current code too much.
I have a rudimentary understanding of Pyqt4 (i will accept tkinter answers also).
I've tried not to use signals/emits because to my knowledge emits
must be sent from a Qthread which would mean a complete overhaul of my code, changing
classes and threads over to Qthreads. Id like to avoid needing to do this if possible.
Here is one example I've attempted.
test.py
class Update(Thread):
def __init__(self):
Thread.__init__(self)
def run(self):
for i in range(10):
time.sleep(2)
import test
wa.label.setText(str(i))
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
self.label = QLabel(" ")
layout = QVBoxLayout()
layout.addWidget(self.label)
self.setLayout(layout)
Update1 = Update()
Update1.start()
Update1.refresh1 = 'ba'
self.label.setText(Update1.refresh1)
if __name__ == "__main__":
app = QApplication(sys.argv)
wa = MyWindow()
wa.show()
sys.exit(app.exec_())
This code works, but my variables need to update from other modules/classes or threads. The moment I move 'class Update' into a new module like THIS:
test.py
import test2
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
self.label = QLabel(" ")
layout = QVBoxLayout()
layout.addWidget(self.label)
self.setLayout(layout)
Update1 = test2.Update()
Update1.start()
Update1.refresh1 = 'ba'
self.label.setText(Update1.refresh1)
if __name__ == "__main__":
app = QApplication(sys.argv)
wa = MyWindow()
wa.show()
sys.exit(app.exec_())
test2.py #updates GUI
class Update(Thread):
def __init__(self):
Thread.__init__(self)
def run(self):
for i in range(10):
time.sleep(2)
import test
test.wa.label.setText(str(i))
I get: AttributeError: 'module' object has no attribute 'wa'
Also, I was also considering putting class Update() into a Qthread, running it from any module/class where a variable has been updated and using the emit function inside Update(). this would solve having to change my current classes/threads to be Qthreads.
If anyone knows of a simple way I could update my GUI simply by calling a class like update() an example would be appreciated
Because wa is only set when __name__ == "__main__" and that only happens when test.py is the main file.
When you do import test, you are running another instance of the test.py file which is not the main script so it has __name__ == 'test' not __main__. So, even if wa was set, you would be changing another instance of it.
Possible solution:
You can get a reference to the __main__ module and set on the test2.py module:
On test.py:
import test2
test2.parent = sys.modules[__name__]
Now, on test2.py (do not import test but make sure test imports test2):
parent.wa.label.setText('Blablabla')

Categories