pythonpyqt5qsettings

I want to save PyQt5 settings when application close


I want to save PyQt5 settings when application close. This is because the next time I run it again, it will run with the final settings. So, I wrote the code like this.

This is a main ui code.

# main_ui.py
import sys
from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QTabWidget, QVBoxLayout
from sub_ui import SubUI


class MainUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.settings = QSettings("A", "B")
        self.init_ui()

    def init_ui(self):
        tabs = QTabWidget()
        tabs.addTab(SubUI(), "SubUI")

        vlayout = QVBoxLayout()
        vlayout.addWidget(tabs)

        central_widget = QWidget()
        central_widget.setLayout(vlayout)
        self.setCentralWidget(central_widget)

        self.setWindowTitle("MainUI")
        if self.settings.contains("geometry"):
            print("LOAD: " + str(self.settings.value("geometry")))
            self.setGeometry(self.settings.value("geometry"))
        else:
            self.setGeometry(100, 100, 300, 300)
        self.show()

    def closeEvent(self, event):
        print("SAVE: " + str(self.settings.value("geometry")))
        self.settings.setValue("geometry", self.frameGeometry())


if __name__ == "__main__":
    APP = QApplication(sys.argv)
    ex = MainUI()
    sys.exit(APP.exec_())

This is a sub ui code.

# sub_ui.py
from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QLineEdit


class SubUI(QWidget):
    def __init__(self):
        super().__init__()
        self.settings = QSettings("A", "B")
        self.label = QLabel("test")
        self.lineedit = QLineEdit()
        self.init_ui()
        self.load_settings()

    def __del__(self):
        self.save_settings()

    def init_ui(self):
        hlayout = QHBoxLayout()
        hlayout.addWidget(self.label)
        hlayout.addWidget(self.lineedit)
        self.setLayout(hlayout)

    def save_settings(self):
        self.settings.setValue("label", self.lineedit.text())

    def load_settings(self):
        if self.settings.contains("label"):
            self.lineedit.setText(self.settings.value("label"))

There is two problems.

1. Changed frameGeometry

I ran this program and just close by clicked [X] icons. But the "self.frameGeometry()" value changes. Why this has happened?

=== More Information ===

I just ran the above program three times and turned it off by clicking the X.

LOAD: PyQt5.QtCore.QRect(1289, 370, 544, 604) 
SAVE: PyQt5.QtCore.QRect(1289, 370, 544, 604) 

LOAD: PyQt5.QtCore.QRect(1288, 339, 546, 636) 
SAVE: PyQt5.QtCore.QRect(1288, 339, 546, 636) 

LOAD: PyQt5.QtCore.QRect(1287, 308, 548, 668) 
SAVE: PyQt5.QtCore.QRect(1287, 308, 548, 668) 

The frameGeometry value changed by itself.

And this is an original problem of my program.

LOAD: PyQt5.QtCore.QRect(852, 217, 1108, 1050)
SAVE: PyQt5.QtCore.QRect(851, 186, 1110, 1082)

LOAD: PyQt5.QtCore.QRect(851, 186, 1110, 1082)
SAVE: PyQt5.QtCore.QRect(850, 155, 1112, 1114)

LOAD: PyQt5.QtCore.QRect(850, 155, 1112, 1114)
SAVE: PyQt5.QtCore.QRect(849, 124, 1114, 1146)

===

How could I make frameGeometry value consistent?

2. Can't save sub_ui settings.

When this program closes, it returns the error.

RuntimeError: wrapped C/C++ object of type QSettings has been deleted

Therefore, I can't save the values.

How could I do what I want to do?


Solution

    1. I don't reproduce it on Linux. But you could use saveGeometry() and restoreGeometry():
    class MainUI(QMainWindow):
        def __init__(self):
            super().__init__()
            self.settings = QSettings("A", "B")
            self.init_ui()
    
        def init_ui(self):
            # ...
            self.setWindowTitle("MainUI")
            self.load_settings()
            self.show()
    
        def closeEvent(self, event):
            self.save_settings()
            super().closeEvent(event)
    
        def save_settings(self):
            self.settings.setValue("geometry", self.saveGeometry())
    
        def load_settings(self):
            if self.settings.contains("geometry"):
                self.restoreGeometry(self.settings.value("geometry"))
            else:
                self.setGeometry(100, 100, 300, 300)
    
    1. In the __del__ method, the objects that depend on SubUI as the child widgets are already destroyed, which is indicated in the error message obtained by the OP.

      On the other hand only the closeEvent() method of the window (MainUI) since it is the only one that closes so that that the appropriate method to save all the data in QSettings implemented a generic method to save the data.

    main_ui.py

    class MainUI(QMainWindow):
        def __init__(self):
            super().__init__()
            self.settings = QSettings("A", "B")
            self.init_ui()
    
        def init_ui(self):
            # ...
    
        def closeEvent(self, event):
            for w in self.findChildren(QWidget) + [self]:
                if hasattr(w, "save_settings") and callable(w.save_settings):
                    w.save_settings()
            super().closeEvent(event)
    
        def save_settings(self):
            self.settings.setValue("geometry", self.frameGeometry())

    sub_ui.py

    from PyQt5.QtCore import QSettings
    from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QLineEdit
    
    
    class SubUI(QWidget):
        def __init__(self):
            super().__init__()
            self.settings = QSettings("A", "B")
            self.label = QLabel("test")
            self.lineedit = QLineEdit()
            self.init_ui()
            self.load_settings()
    
        def init_ui(self):
            hlayout = QHBoxLayout(self)
            hlayout.addWidget(self.label)
            hlayout.addWidget(self.lineedit)
    
        def save_settings(self):
            self.settings.setValue("label", self.lineedit.text())
    
        def load_settings(self):
            if self.settings.contains("label"):
                self.lineedit.setText(self.settings.value("label"))