Cannot update one page Text Label after an event from another page using PyQt5 [duplicate]
I have a PySide2 GUI that accepts a number from the user on page one then does some calculations and displays the results on page two. Each page is a QWidget within a QStackedWidget. There is a pushbutton on page two, the results page, that sends the user back to page one to enter a new number.
My problem is that when I enter a new number the results never change from the first number. I use print statements to confirm that the labels on the results page are updating but the display stays the same.
# importing the module
import os
import sys
from PySide2 import QtWidgets
import PySide2.QtUiTools as QtUiTools
class IncomeScreen(QtWidgets.QMainWindow):
def __init__(self):
super(IncomeScreen, self).__init__()
# Load the IncomeScreen ui
loader = QtUiTools.QUiLoader()
path = os.path.join(os.path.dirname(__file__), "main.ui")
self.main = loader.load(path, self)
# Connect the signals with custom slots
self.main.calculate_pushButton.clicked.connect(self.calculate)
def calculate(self):
init_amount = self.main.income_lineEdit.text()
IncomeScreen.init_amount = float(init_amount)
# Create an instance of DistributionScreen class
self.distribution = DistributionScreen()
# Add DistributionScreen to the stacked widget
widget.addWidget(self.distribution)
# Change index to show DownloadPage
widget.setCurrentIndex(widget.currentIndex()+1)
class DistributionScreen(QtWidgets.QMainWindow):
def __init__(self):
super(DistributionScreen, self).__init__()
loader = QtUiTools.QUiLoader()
path = os.path.join(os.path.dirname(__file__), "dialog.ui")
self.dialog = loader.load(path, self)
# Set initial amount to label
self.dialog.initialAmount_label.setText(str(IncomeScreen.init_amount))
print("Initial Amount = {:0.2f}".format(IncomeScreen.init_amount))
# 10 Percent
ten = IncomeScreen.init_amount * 0.1
print("10% = {:0.2f}".format(ten))
self.dialog.label_10percent.setText("{:0.2f}".format(ten))
print(self.dialog.label_10percent.text())
# 20 percent
twenty = IncomeScreen.init_amount * 0.2
print("20% = {:0.2f}".format(twenty))
self.dialog.label_20percent.setText("{:0.2f}".format(twenty))
print(self.dialog.label_20percent.text())
# Update widget
self.dialog.update()
# Connect the signals with custom slots
self.dialog.reset_pushButton.clicked.connect(self.reset)
def reset(self):
print("reset")
# Change index to show IncomeScreen
widget.setCurrentIndex(widget.currentIndex()-1)
# main
# if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
income = IncomeScreen()
widget = QtWidgets.QStackedWidget()
widget.addWidget(income)
widget.show()
try:
sys.exit(app.exec_())
except:
print("Exiting")
Also I'm using Python 3.7.4
EDIT: You can download the ui files here
Solution 1:
There are various problems with your code, but the most important one is that every time calculate
is called, a new DistributionScreen
is added to the stacked widget, but widget.setCurrentIndex(widget.currentIndex()+1)
will always go to the second index of the stacked widget (which is the first instance you created).
A possible simple workaround could be to use the index of the widget returned by addWidget
or use setCurrentWidget
:
def calculate(self):
init_amount = self.main.income_lineEdit.text()
IncomeScreen.init_amount = float(init_amount)
self.distribution = DistributionScreen()
index = widget.addWidget(self.distribution)
widget.setCurrentIndex(index)
# alternatively:
widget.setCurrentWidget(self.distribution)
Unfortunately, while this would make your code work, it's not a valid solution, as there are other important issues that would create other problems sooner or later:
- a stacked widget works like a tab widget: it's intended to allow reusability of the widgets; you should not constantly create a new instance every time, but possibly use the existing one;
- you should not set nor use a class attribute for a variable that depends on an instance (as you did with
IncomeScreen.init_amount
); - you're adding QMainWindows to a stacked widget, which is discouraged, as a main window should be used as a top level window (it has features that rely on that aspect); note that even QDialog is not a valid candidate, and you should opt for a basic QWidget or a container like QFrame or QGroupBox;
- you're using
QUiLoader
to load the widget as a child of the main window, but without adding it to a layout (or setting as central widget), and this will make it unable to resize itself whenever the top level window is resized: if the main window becomes too small, some of the contents won't be visible, if it's too big there will be a lot of unused space; - you're trying to access a global variable (
widget
) from an instance, while it's not guaranteed that the variable would be valid; in any case, it should not be the instance to create new widgets and set the index of the stacked widget, but the stacked widget itself (or any of its ancestors); - the last try/except block is very dangerous, as it prevents you to capture exceptions (since it's a generic
except:
) or know what was wrong with your program if it crashes;
This is a possible revision of your code (untested, as you didn't provide the ui
files).
import os
import sys
from PySide2 import QtWidgets, QtCore
import PySide2.QtUiTools as QtUiTools
class IncomeScreen(QtWidgets.QWidget):
# a custom signal to notify that we want to show the distribution page
# with the provided value
goToDistribution = QtCore.Signal(float)
def __init__(self):
super(IncomeScreen, self).__init__()
# Load the IncomeScreen ui
loader = QtUiTools.QUiLoader()
path = os.path.join(os.path.dirname(__file__), "main.ui")
self.main = loader.load(path, self)
# a proper layout that manages the contents loaded with QUiLoader
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.main)
# Connect the signals with custom slots
self.main.calculate_pushButton.clicked.connect(self.calculate)
def calculate(self):
init_amount = self.main.income_lineEdit.text()
self.goToDistribution.emit(float(init_amount))
class DistributionScreen(QtWidgets.QWidget):
reset = QtCore.Signal()
def __init__(self):
super(DistributionScreen, self).__init__()
loader = QtUiTools.QUiLoader()
path = os.path.join(os.path.dirname(__file__), "dialog.ui")
self.dialog = loader.load(path, self)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.dialog)
self.dialog.reset_pushButton.clicked.connect(self.reset)
def setIncome(self, value):
# Set initial amount to label
self.dialog.initialAmount_label.setText(str(value))
print("Initial Amount = {:0.2f}".format(value))
# 10 Percent
ten = value * 0.1
print("10% = {:0.2f}".format(ten))
self.dialog.label_10percent.setText("{:0.2f}".format(ten))
print(self.dialog.label_10percent.text())
# 20 percent
twenty = value * 0.2
print("20% = {:0.2f}".format(twenty))
self.dialog.label_20percent.setText("{:0.2f}".format(twenty))
print(self.dialog.label_20percent.text())
class MainWidget(QtWidgets.QStackedWidget):
def __init__(self):
super(MainWidget, self).__init__()
# create *both* the pages here
self.income = IncomeScreen()
self.addWidget(self.income)
self.distribution = DistributionScreen()
self.addWidget(self.distribution)
self.income.goToDistribution.connect(self.goToDistribution)
self.distribution.reset.connect(self.reset)
def goToDistribution(self, value):
# we received the notification signal, then we set the value and
# show the related page by switching to it
self.distribution.setIncome(value)
self.setCurrentWidget(self.distribution)
def reset(self):
self.setCurrentWidget(self.income)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWidget = MainWidget()
mainWidget.show()
sys.exit(app.exec_())
Note that:
- if you want a numeric control, you should use QSpinBox or QDoubleSpinBox (for floating point numbers), or set a QIntValidator or QDoubleValidator, otherwise if the user enters a non numeric value your program will crash (due to the usage of
float()
done without previously checking if the string is actually a valid number); - while QUiLoader is useful, it has the drawback of always creating a widget, so you can never override its methods; the only solution to this is to use files generated by
pyside-uic
and use the multiple inheritance method, or switch to PyQt and use itsuic.loadUi
which instead allows setting up the UI on the current widget; - most of the problems in your code are due to some tutorials that have been shared lately (some of them on youtube): unfortunately, those tutorials suggest a lot of terrible things that should not be done, both for PyQt and Python; I strongly suggest you to look for other resources and, most importantly, always study the documentation.