Navigation between windows on PyQt5 [duplicate] - python

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

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 its uic.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.

Related

Switch back and forth between windows in pyqt5 while avoiding circular imports

So I have 2 windows I want to be able to switch between, Login and MainWindow, each one is QWidget in its own seperate file, loginUI.py and MainUI.py respectively.
I can easily switch from login to main upon correct authentification by creating a new instance of MainWindow. But in MainWindow I want to have a 'Disconnect' button that shows the Login screen.
Since both are in different files, importing in this scenario raises a circular import error in python.
Other approaches I tried are:
Using signals and handling them in an intermediate file. This is fine but as I add more buttons/ windows the file started to become a bit of a mess.
Passing the instance of Login to MainWindow.__init__(self, login), and just using self.login.show(). Which seems like a good way, but again as I add more and more windows, I'm scared it might affect performance as so many instances are just running in the background.
Is any of these the correcte way or am I missing an easier way
Edit:
login.py
from PyQt5.QtWidgets import QWidget, QPushButton, QLineEdit
from PyQt5.QtCore import QSize
from mainmenu import MainWindow
from PyQt5 import QtWidgets
import sys
class Login(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setMinimumSize(QSize(300, 200))
self.setWindowTitle("Log in")
self.username = QLineEdit(self)
self.username.move(50, 10)
self.password = QLineEdit(self)
self.password.move(50, 40)
self.connect_button = QPushButton('Connect', self)
self.connect_button.move(50, 100)
self.connect_button.clicked.connect(self.handleConnexion)
def handleConnexion(self):
if self.username.text() == "admin" and self.password.text()=="1":
self.mw = MainWindow()
self.mw.show()
self.close()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWin = Login()
mainWin.show()
sys.exit( app.exec_() )
mainmenu.py
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(300, 200))
self.setWindowTitle("Main menu")
disconnect_button = QPushButton('Disconnect', self)
disconnect_button.clicked.connect(self.handeDC)
def handeDC(self):
pass
# here I either send a signal to handle it somewhere else
# or
# if i pass the login instance in __init__, just do login.show()
Note: since this is a common question, I'll provide a more broad answer that better reflects the object hierarchy.
Since the "main" window is, as the name suggests, the main one, the script containing it should be the main one, while the login window should be imported and eventually shown as required.
The hierarchy is important: you don't have to consider the order in which the windows are shown, but their relevance.
Considering this, the main script will:
create the main window;
show the login if required;
show the main window if the login is successful;
show again the login if the user disconnects;
clear the contents if the user has changed (*see below);
The above also shows why it's rarely a good idea to continuously create new instances of windows.
The login window should also be a QDialog, which makes things easier: the exec() method is "blocking" (for the function execution, not for the event loop), and waits until the dialog is accepted or *rejected.
main.py
from PyQt5.QtWidgets import *
from login import Login
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setMinimumSize(300, 200)
self.setWindowTitle('Main menu')
disconnect_button = QPushButton('Disconnect')
self.setCentralWidget(disconnect_button)
# only for a *persistent* login dialog (*see below)
self.login = Login()
disconnect_button.clicked.connect(self.disconnect)
def start(self):
# put here some function that might check for a 'previous' logged in
# state, possibly stored using QSettings.
# in this case, we just assume that the user has never previously
# logged in, so we automatically show the login window; if the above
# function returns True instead, we can safely show the main window
logged = False
if logged:
self.show()
else:
self.showLogin()
def disconnect(self):
self.hide()
self.showLogin()
def showLogin(self):
if self.login.exec():
self.show()
# alternatively (*see below):
login = Login()
if login.exec():
self.show()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.start()
sys.exit(app.exec())
login.py
from PyQt5.QtWidgets import *
class Login(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setMinimumSize(300, 200)
self.setWindowTitle('Log in')
self.username = QLineEdit()
self.password = QLineEdit()
self.password.setEchoMode(self.password.Password)
self.connect_button = QPushButton('Connect', enabled=False)
layout = QGridLayout(self)
layout.addWidget(QLabel('Username:'), 0, 0)
layout.addWidget(self.username, 0, 1)
layout.addWidget(QLabel('Password:'), 1, 0)
layout.addWidget(self.password, 1, 1)
layout.addWidget(self.connect_button, 2, 0, 1, 2)
self.connect_button.clicked.connect(self.handleConnexion)
self.username.textChanged.connect(self.checkFields)
self.password.textChanged.connect(self.checkFields)
def checkFields(self):
if self.username.text() and self.password.text():
self.connect_button.setEnabled(True)
else:
self.connect_button.setEnabled(False)
def handleConnexion(self):
if self.username.text() == 'admin' and self.password.text() == '1':
self.accept()
else:
QMessageBox.warning(self, 'Error',
'Invalid username or password!')
Notes:
the above code will always show the existing Login instance, so the username and password fields will "remember" the previous entries; if you don't want that, you can always call clear() on those fields by overriding the exec() (but remember to call the base implementation and return its result!); alternatively, don't create the self.login and always create a new, local instance of Login() in showLogin();
you shall always use layout managers, and never rely on fixed geometries;
QMainWindow should always have a central widget, creating children of a main window using it as the parent is discouraged (unless you really know what you're doing); if you need more widgets, use a basic QWidget, set a layout for it, add the children, and finally call setCentralWidget();
more complex hierarchies can require a "controller" (read more about the MVC pattern) to better organize the whole program and respect the OOP patterns; this is normally achieved by a basic class or by subclassing the QApplication;
about the last (*) point in my initial list, and related to what explained above: a "controller" could/should completely delete the previous "main window" (it would be both easier and safer) and eventually show a new instance whenever the user has disconnected;
Instead of closing the Login widget when creating the MainWindow widget, you could just hide the widget, which will save the overhead of creating a new instance and also keep the connected slots intact.
Then on your MainWindow you can create a diconnected signal that should be emited when the user clicks the disconnect button.
The login window can listen for the signal and call it's show method.
I made inline comments where I made changes in the example below:
mainwindow.py
from PyQt5.QtCore import pyqtSignal # added this
class MainWindow(QMainWindow):
disconnected = pyqtSignal() # added this
def __init__(self):
QMainWindow.__init__(self)
...
disconnect_button = QPushButton('Disconnect', self)
disconnect_button.clicked.connect(self.handeDC)
def handeDC(self):
# ... do some stuff
self.disconnected.emit() # added this
login.py
class Login(QWidget):
def __init__(self):
QWidget.__init__(self)
...
def handleConnexion(self):
if self.username.text() == "admin" and self.password.text()=="1":
self.mw = MainWindow()
self.mw.disconnected.connect(self.show) # added this
self.mw.show()
self.hide() # changed this
...

How do I refresh QWidget to display the current information?

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
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 its uic.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.

How to store the last checked item as default, when Opening next time , in PyQt5 QRadioButton?

It's my code. How to store the lastly clicked/checked button as default in the next opening of the programme? For example: If we run the programme and click the 3rd button and close the entire programme. Whenever I reopen/re-run the programme, the lastly clicked button is checked by default ( ie. third button is checked by default)
import sys
from PyQt5 import QtWidgets
class RadioButton(QtWidgets.QWidget):
def __init__(self):
super(). __init__()
self.setWindowTitle("Radio Button")
self.rbtn1 = QtWidgets.QRadioButton("Button1")
self.rbtn1.clicked.connect(self.getvalue)
self.rbtn2 = QtWidgets.QRadioButton("Button2")
self.rbtn2.clicked.connect(self.getvalue)
self.rbtn3 = QtWidgets.QRadioButton("Button3")
self.rbtn3.clicked.connect(self.getvalue)
self.rbtn4 = QtWidgets.QRadioButton("Button4")
self.rbtn4.clicked.connect(self.getvalue)
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(self.rbtn1)
vbox.addWidget(self.rbtn2)
vbox.addWidget(self.rbtn3)
vbox.addWidget(self.rbtn4)
self.setLayout(vbox)
def getvalue(self):
if self.rbtn1.isChecked():
self.rbtn1.setChecked(True)
print("Radio button 1 is checked")
elif self.rbtn2.isChecked():
self.rbtn2.setChecked(True)
print("Radio button 2 is checked")
elif self.rbtn3.isChecked():
self.rbtn3.setChecked(True)
print("Radio button 3 is checked")
elif self.rbtn4.isChecked():
self.rbtn4.setChecked(True)
print("Radio button 4 is checked")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
win = RadioButton()
win.show()
sys.exit(app.exec_())
You can use QSettings to save and restore data. Note that values are serialized as QVariants, so you cannot, for instance, save a custom python subclass instance.
In order to properly use QSettings you must set both the Qt application name and organization name (otherwise data won't be stored anywhere).
It is possible to save settings in other ways, but for general usage it's better to use the default behavior.
Note that, since values are stored as strings, Qt can only try to guess the actual type, so it's always better to specify the type when using value() (see the case below).
Then, while by default Qt is able to automatically group radio buttons and make them exclusive (see the autoExclusive property of QAbstractButton, which is set to True for QRadioButtons), for this kind of situations it's better to rely on a QButtonGroup, which not only allows better control on the buttons, but also allows identifying buttons member of the group by using unique IDs they were assigned to.
Finally, it's just a matter of connecting the appropriate signal of the button group to the function that saves the currently checked button.
from PyQt5 import QtWidgets, QtCore
class RadioButton(QtWidgets.QWidget):
def __init__(self):
super(). __init__()
self.setWindowTitle("Radio Button")
self.rbtn1 = QtWidgets.QRadioButton("Button1")
self.rbtn2 = QtWidgets.QRadioButton("Button2")
self.rbtn3 = QtWidgets.QRadioButton("Button3")
self.rbtn4 = QtWidgets.QRadioButton("Button4")
buttons = self.rbtn1, self.rbtn2, self.rbtn3, self.rbtn4
vbox = QtWidgets.QVBoxLayout(self)
self.optionGroup = QtWidgets.QButtonGroup()
for i, button in enumerate(buttons):
vbox.addWidget(button)
self.optionGroup.addButton(button, i)
# for Qt>=5.15:
# self.optionGroup.idToggled.connect(self.getvalue)
# otherwise:
self.optionGroup.buttonToggled[int, bool].connect(self.getvalue)
self.settings = QtCore.QSettings()
# read the value if it exists, otherwise use a default; note that
# the type MUST be specified, otherwise numbers would be read as
# strings, raising an exception and crashing the program in this case
default = self.settings.value('MyOption', 2, type=int)
self.optionGroup.button(default).setChecked(True)
def getvalue(self, id, checked):
if checked:
button = self.optionGroup.button(id)
print("{} is checked".format(button.text()))
self.settings.setValue('MyOption', id)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
app.setOrganizationName('MyOrganization')
app.setApplicationName('MyApplication')
win = RadioButton()
win.show()
sys.exit(app.exec_())
I strongly recommend you to carefully study the whole documentation about all the classes specified above.

Clearing QTableView in PyQt5

I am working with PyQt5 and I am trying to clear a QTreeView by pressing a button. The program is supposed to take a path from the user with a QLineEdit and then display it on the TreeView. So far it's working great. The thing is that I can't figure out how to clear the view once I'm finished or maybe if I typed in the wrong path. I know that I could just use the clear() function but it works only with a Widget, not with a View. If I use the reset() function it just displays the "This PC" folder without completey clearing the Tree.
I am going to include the code just in case.
from PyQt5 import QtWidgets as qtw
import sys
class MainWindow(qtw.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Init UI
self.tree = qtw.QTreeView()
self.model = qtw.QFileSystemModel()
# Items
self.path_input = qtw.QLineEdit()
path_label = qtw.QLabel("Enter a path to begin: ")
check_btn = qtw.QPushButton("Check") # To display the items
clear_btn = qtw.QPushButton("Clear") # To clear the TreeView
start_btn = qtw.QPushButton("Start") # Not implemented yet
# Layouts
top_h_layout = qtw.QHBoxLayout()
top_h_layout.addWidget(path_label)
top_h_layout.addWidget(self.path_input)
top_h_layout.addWidget(check_btn)
bot_h_layout = qtw.QHBoxLayout()
bot_h_layout.addWidget(clear_btn)
bot_h_layout.addWidget(start_btn)
main_v_layout = qtw.QVBoxLayout()
main_v_layout.addLayout(top_h_layout)
main_v_layout.addWidget(self.tree)
main_v_layout.addLayout(bot_h_layout)
self.setLayout(main_v_layout)
check_btn.clicked.connect(self.init_model)
clear_btn.clicked.connect(self.clear_model)
self.show()
def init_model(self):
self.model.setRootPath(self.path_input.text())
self.tree.setModel(self.model)
self.tree.setRootIndex(self.model.index(self.path_input.text()))
def clear_model(self):
self.tree.reset() # This is where I get the problem
if __name__ == "__main__":
app = qtw.QApplication(sys.argv)
w = MainWindow()
sys.exit(app.exec_())
You can't "clear a view", as the view only shows the model's content, it cannot directly modify (or "clear") it. Also, reset() does not do what you're trying to achieve:
void QAbstractItemView::reset()
Reset the internal state of the view.
The state, not the model. As you can see, in your case it only resets the root node in its collapsed state.
What you need to clear is the model, but since you clearly cannot clear a filesystem model, the solution is to set the view's state as it was before starting the search: by removing the model from the view.
def clear_model(self):
self.tree.setModel(None)

Qt - keep reference of widget in Python

In reference to a previous question, I need some help with keeping references in my application.
First a snippet from my code.
from PyQt4 import QtGui
import os, os.path
import sys
class mainWindowHandler():
equationEditor = []
_listview = None
_window = None
def __init__(self):
return
def showAddEquation(self):
"""Creates a new instance of the dynamic editor for adding an equation"""
#create a horizontal split layout
window = QtGui.QWidget()
layout = QtGui.QHBoxLayout()
current = len(self.equationEditor) - 1
de = QtGui.QPushButton(str(current))
self.equationEditor.append(de)
de.clicked.connect(self.clicked)
#fill list view with items from equation collection
listview = QtGui.QListWidget()
for equation in self.equationEditor:
item = QtGui.QListWidgetItem()
item.setText(equation.text())
listview.addItem(item)
layout.addWidget(listview)
layout.addWidget(de)
window.setWindowTitle("Equation {}".format(str(current))
window.setLayout(layout)
self._window = window
self._listview = listview
window.show()
return window
def clicked(self):
"""Method for handling the button events in the solver settings\n
signal = the button hit\n"""
return self.showAddEquation()
if __name__ == "__main__":
path = os.path.dirname(os.path.abspath(__file__))
app = QtGui.QApplication(sys.argv)
ewh = mainWindowHandler()
window = ewh.showAddEquation()
sys.exit(app.exec_())
The application will (later) create a window that allows the manipulation of certain settings - in my code example represented by the QPushButton. These settings are later written to a txt-file, but until then I save them in form of there widget. I simply add the widget to a collection and recall them from there. That works well on the Python-level.
Now, I have a button that creates a new instance of the window from inside of the window itself. That works too. But only until the third instance. At that point I loose the reference to my QPushButton on the Qt-level. I get the
wrapped C/C++ object of type `QPushButton` has been deleted
error when trying to retrieve the buttons from my collection (equationEditor). In Python they are still there, but obviously the corresponding Qt-objects where destroyed because I somewhere mishandled the references.
Can someone point out a better solution or how I can keep the references?
Thanks...
Edit:
As there seem to be some confusions I will try to explain the functionality a bit more in detail.
The program starts and creates a window "Equation 1" with a QListViewand a QPushButton "1". In the list view all available QPushButton are listed (at start only 1 item). In my actual program the QPushButton is QWidget with some text fields and the QPushButton.
If the user clicks "1" then the button "1" should disappear and a new instance of QPushButton named "2" should appear at the position of "1". Additionally, the listview should now hold two items "1" and "2" and the window should have the title "Equation 2". If it is a new window or the same as before with new contents is not relevant. Both variants would be okay. The former is the way it is implemented at the moment. Visible should by only one window at a time.
All instances of QPushButton should by collected in a small list (called equationEditor) to keep them in the memory. In my actual program this is used for saving all changes made in the widgets without writing the changes to a temp file.
Later, if the user selects item "1" in the QListView then the current visible QPushButton should be replaced by the QPushButton "1" (from the collection equationEditor) or if he selects the second item the QPushButton "2" should be shown.
Why?
The widget that will be used later contains a lot of editable data. As the user could edit that at any time it is easier to keep the widgets in memory without showing them instead of repopulating all the data again. As soon as the user selects one in the QListView the corresponding widget should be shown in the window so that he can edited the data in the widget again.
It's quite hard to understand what exactly you're trying to do. Looking at your code I wonder why it is even working twice before failing.
Btw. I just saw, that there is a quite accurate description of why it's failing in the previous post, given by Schollii
Anyways, I think you should make a new class for the equation window. The main class can then keep track of all opened windows in the equationEditor list. It can also add the values of the other opened windows to a new one, once created.
Here is how it would look like
from PyQt4 import QtGui
import os, os.path
import sys
class ShowAddEquation(QtGui.QWidget):
"""Creates a new instance of the dynamic editor for adding an equation"""
def __init__(self,parent=None):
super(ShowAddEquation, self).__init__(parent=parent)
#create a horizontal split layout
layout = QtGui.QHBoxLayout()
self.current = 0
self.de = QtGui.QPushButton(str(self.current))
self.listview = QtGui.QListWidget()
layout.addWidget(self.listview)
layout.addWidget(self.de)
self.setWindowTitle("Equation Editor")
self.setLayout(layout)
self.show()
def setCurrent(self, current):
self.current=current
self.de.setText(str(self.current))
class mainWindowHandler():
equationEditor = []
def __init__(self):
return
def clicked(self):
se = ShowAddEquation()
self.equationEditor.append(se)
se.de.clicked.connect(self.clicked)
current = len(self.equationEditor) - 1
se.setCurrent(current)
for equation in self.equationEditor:
item = QtGui.QListWidgetItem()
item.setText(str(equation.current))
se.listview.addItem(item)
if __name__ == "__main__":
path = os.path.dirname(os.path.abspath(__file__))
app = QtGui.QApplication(sys.argv)
ewh = mainWindowHandler()
ewh.clicked()
sys.exit(app.exec_())
So, after understanding the approach given in the first answer I have solved my problem. Here is the working code
# -*- coding: utf-8 -*-
"""
Created on Sat Sep 3 14:31:15 2016
"""
from PyQt4 import QtGui
from PyQt4 import QtCore
import os, os.path
import sys
class mainWindowHandler():
equationEditor = []
_listview = None
_window = None
def __init__(self):
return
def showAddEquation(self):
"""Creates a new instance of the dynamic editor for adding an equation"""
#create a horizontal split layout
self._window = QtGui.QWidget()
layout = QtGui.QHBoxLayout()
self._listview = QtGui.QListWidget()
layout.addWidget(self._listview)
self._listview.clicked[QtCore.QModelIndex].connect(self.changed)
self._window.setLayout(layout)
#populate the right side of the layout with a button
self.clicked()
self._window.show()
return self._window
def clicked(self):
"""Make a new button instance and add it to the window and the collection"""
window = self._window
layout = window.layout()
current = len(self.equationEditor) - 1
de = QtGui.QPushButton(str(current))
self.equationEditor.append(de)
de.clicked.connect(self.clicked)
#close the currently shown button
item = layout.takeAt(1)
if item is not None:
item.widget().close()
layout.addWidget(de)
#fill list view with items from material collection
item = QtGui.QListWidgetItem()
item.setText(de.text())
self._listview.addItem(item)
self._window.setWindowTitle("Equation Editor {}".format(str(current)))
def changed(self, index):
"""hide the object on the right side of the layout and show the button at position index in the collection"""
layout = self._window.layout()
item = layout.takeAt(1)
item.widget().close()
# insert the selected button from the collection
de = self.equationEditor[index.row()]
layout.insertWidget(1, de)
self._window.setWindowTitle("Equation Editor {}".format(str(index.row() - 1)))
de.show()
if __name__ == "__main__":
path = os.path.dirname(os.path.abspath(__file__))
app = QtGui.QApplication(sys.argv)
ewh = mainWindowHandler()
window = ewh.showAddEquation()
sys.exit(app.exec_())

Categories