I would like the ability to save and restore the widget state (properties and values) in my PyQt5 app. The widget state should be saved to an .ini file. This has already been demonstrated in the following Stackoverflow answers:
- How to save text in QLineEdits in PyQt even if the Widget gets closed?
- Loading Widgets properly while restoring from QSettings
However, the answers to above questions do not address a specific issue I am facing in my example code below. I want the saving and restoring from the settings .ini file to be unique for every instance of the parent widget (main window app) that is running. Therefore, when saving the widget properties, the iteration over all the widgets should only occur on the children of the parent widget (main window) and not through all the widgets that are currently running in the global application.
I think the issue in my example code below is related to the line for w in QtWidgets.qApp.allWidgets():
. I think this line iterates over ALL the widgets that are currently open in PyQt5 global application. However, when there are multiple instances of the same parent widget, there will be duplication in the objectName. Although a unique name can be given to each instance (ex: app_name
) during init() and be accounted for in the QSettings key, this may not be the best universally adaptable solution. Therefore how can I fix the issue I am facing? How can I get settings_save()
function to iterate through all the children widget of the instance of the parent widget (main window app) and NOT over ALL the parent widgets currently running in the global application? In Qt docs, I can't find a function similar to allWidgets()
that will allow me to specify the parent widget (ex: QMainWindow) and give me all the widgets and objects under it. If getting all the widgets inside a parent widget was possible, then I can easily modify the function settings_save()
include not only the QSetting variable as the argument, but also the instance of the widget for which I want to save the settings for.
main_app.py
import sys
from PyQt5 import QtWidgets, uic, QtCore, QtGui
from PyQt5.QtCore import QFileInfo, QSettings, QObject
from PyQt5.QtWidgets import qApp
from ui_mainwindow import Ui_MainWindow
def settings_value_is_valid(val):
# https://stackoverflow.com/a/60028282/4988010
if isinstance(val, QtGui.QPixmap):
return not val.isNull()
return True
def settings_restore(settings):
# https://stackoverflow.com/a/60028282/4988010
finfo = QtCore.QFileInfo(settings.fileName())
if finfo.exists() and finfo.isFile():
for w in QtWidgets.qApp.allWidgets():
if w.objectName() and not w.objectName().startswith("qt_"):
# if w.objectName():
mo = w.metaObject()
for i in range(mo.propertyCount()):
prop = mo.property(i)
name = prop.name()
last_value = w.property(name)
key = "{}/{}".format(w.objectName(), name)
# print(prop, name, last_value, key)
if not settings.contains(key):
continue
val = settings.value(key, type=type(last_value),)
if (
val != last_value
and settings_value_is_valid(val)
and prop.isValid()
and prop.isWritable()
):
w.setProperty(name, val)
def settings_save(settings):
# https://stackoverflow.com/a/60028282/4988010
for w in QtWidgets.qApp.allWidgets():
if w.objectName() and not w.objectName().startswith("qt_"):
# if w.objectName():
mo = w.metaObject()
for i in range(mo.propertyCount()):
prop = mo.property(i)
name = prop.name()
key = "{}/{}".format(w.objectName(), name)
val = w.property(name)
if settings_value_is_valid(val) and prop.isValid() and prop.isWritable():
settings.setValue(key, w.property(name))
class MyApp(QtWidgets.QMainWindow):
def __init__(self, app_name='DefaultAppName'):
super(MyApp, self).__init__()
self.settings = QSettings("./temp/gui_settings-{}.ini".format(app_name), QSettings.IniFormat)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# Set window titlte and qlineedit to indicate the name of app instance
self.ui.app_instance_name.setText(app_name)
self.setWindowTitle(app_name)
# Load the saved config file saved from previous app usage
self.config_widgets_load_settings()
self.ui.action_save_current_config.triggered.connect(self.config_widgets_save_settings)
self.ui.action_load_config.triggered.connect(self.config_widgets_load_settings)
self.ui.action_clear_config_file.triggered.connect(self.config_clear_settings)
def config_widgets_save_settings(self):
# Write current state to the settings config file
settings_save(self.settings)
def config_widgets_load_settings(self):
# Load settings config file
settings_restore(self.settings)
def config_clear_settings(self):
# Clear the settings config file
self.settings.clear()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
# First instance of the MyApp
app_one = MyApp(app_name='App#1')
app_one.show()
# Second instance of the MyApp
app_two = MyApp(app_name='App#2')
app_two.show()
app.exec_()
ui_mainwindow.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(330, 202)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayoutWidget = QtWidgets.QWidget(self.centralwidget)
self.gridLayoutWidget.setGeometry(QtCore.QRect(20, 80, 291, 51))
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.lineEdit1 = QtWidgets.QLineEdit(self.gridLayoutWidget)
self.lineEdit1.setObjectName("lineEdit1")
self.gridLayout.addWidget(self.lineEdit1, 2, 0, 1, 1)
self.pushButton1 = QtWidgets.QPushButton(self.gridLayoutWidget)
self.pushButton1.setCheckable(True)
self.pushButton1.setObjectName("pushButton1")
self.gridLayout.addWidget(self.pushButton1, 2, 1, 1, 1)
self.spinBox1 = QtWidgets.QSpinBox(self.gridLayoutWidget)
self.spinBox1.setMaximum(10000)
self.spinBox1.setProperty("value", 37)
self.spinBox1.setObjectName("spinBox1")
self.gridLayout.addWidget(self.spinBox1, 2, 2, 1, 1)
self.label = QtWidgets.QLabel(self.gridLayoutWidget)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 1, 0, 1, 3)
self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(50, 20, 241, 31))
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.label_2 = QtWidgets.QLabel(self.horizontalLayoutWidget)
self.label_2.setObjectName("label_2")
self.horizontalLayout.addWidget(self.label_2)
self.app_instance_name = QtWidgets.QLineEdit(self.horizontalLayoutWidget)
self.app_instance_name.setObjectName("app_instance_name")
self.horizontalLayout.addWidget(self.app_instance_name)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 330, 21))
self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.action_save_current_config = QtWidgets.QAction(MainWindow)
self.action_save_current_config.setObjectName("action_save_current_config")
self.action_load_config = QtWidgets.QAction(MainWindow)
self.action_load_config.setObjectName("action_load_config")
self.action_clear_config_file = QtWidgets.QAction(MainWindow)
self.action_clear_config_file.setObjectName("action_clear_config_file")
self.menuFile.addAction(self.action_save_current_config)
self.menuFile.addAction(self.action_load_config)
self.menuFile.addAction(self.action_clear_config_file)
self.menubar.addAction(self.menuFile.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.lineEdit1.setText(_translate("MainWindow", "This is default text"))
self.pushButton1.setText(_translate("MainWindow", "Push me"))
self.label.setText(_translate("MainWindow", "My simple app with various widgets"))
self.label_2.setText(_translate("MainWindow", "App instance name"))
self.menuFile.setTitle(_translate("MainWindow", "File"))
self.action_save_current_config.setText(_translate("MainWindow", "Save current config"))
self.action_load_config.setText(_translate("MainWindow", "Load config"))
self.action_clear_config_file.setText(_translate("MainWindow", "Clear config file"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
I figured out the solution after digging through Qt documentations further. Every QWidget is of type QObject, which has a method called findChildren(). This method can be used to specify the parent widget for which you want to save/restore the settings for.
Below are the methods created that will help assist in saving/restoring the settings. My contribution is the creation of the function
settings_get_all_widgets()
. The saving/restoring functions were originally created by @eyllanesc which were modified by me as a solution to this question.Below is the self contained working code which contains the solution to the original question.