Save and restore the values of all widgets of a form

hi!

i have developed an app with pyside6 (i think it also would work with pyqt6) with a form with many widgets (QLineEdit, QCheckBox, QComboBox).

i want to know how i can save and restore all the values of all widgets.

the only thing i found was self._dict_ but that’s a very long list and i couldn’t find a way to save it.

thanks

Hi @HeinKurz there are various ways to do this. The one I prefer is to use a separate model (which can just be a dictionary) and then sync that with the widgets. You can get clever and sync these things automatically, or just iterate over the widgets in a standard loop & assign the values into the dictionary, or pull them out.

Below is a simple example from my book.

import sys

from PySide6.QtWidgets import (
    QApplication,
    QComboBox,
    QFormLayout,
    QLineEdit,
    QMainWindow,
    QPushButton,
    QSpinBox,
    QWidget,
)

# Model is a simple dictionary to start.
model = { 
    "name": "Johnina Smith",
    "age": 10,
    "favorite_icecream": "Vanilla",
}


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        layout = QFormLayout()

        # Dictionary to store the form data, with default data.
        self.name = QLineEdit()
        self.name.setText(model["name"])
        self.name.textChanged.connect(
            self.handle_name_changed
        )
        self.age = QSpinBox()
        self.age.setRange(0, 200)
        self.age.setValue(model["age"])
        self.age.valueChanged.connect(self.handle_age_changed)
        self.icecream = QComboBox()
        self.icecream.addItems(
            ["Vanilla", "Strawberry", "Chocolate"]
        )
        self.icecream.setCurrentText(model["favorite_icecream"])
        self.icecream.currentTextChanged.connect(
            self.handle_icecream_changed
        )

        self.save_btn = QPushButton("Save")
        self.restore_btn = QPushButton("Restore")

        layout.addRow("Name", self.name)
        layout.addRow("Age", self.age)
        layout.addRow("Favorite Ice cream", self.icecream)
        layout.addRow(self.save_btn)
        layout.addRow(self.restore_btn)

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

    def handle_name_changed(self, name):
        model["name"] = name
        print(model)

    def handle_age_changed(self, age):
        model["age"] = age
        print(model)

    def handle_icecream_changed(self, icecream):
        model["favorite_icecream"] = icecream
        print(model)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

You can take this further, for example: below we use a dictionary subclass which fires off a signal when any value is changed. You can use this to “listen” to changes on the model, and update other windows.

import random
import sys


from collections import UserDict
from PySide6.QtCore import QObject, Signal

from PySide6.QtWidgets import (
    QApplication,
    QCheckBox,
    QComboBox,
    QFormLayout,
    QLineEdit,
    QMainWindow,
    QPushButton,
    QSpinBox,
    QWidget,
)



class DataModelSignals(QObject):
    # Emit an "updated" signal when a property changes.
    updated = Signal()


class DataModel(UserDict):
    def __init__(self, *args, **kwargs):
        self.signals = DataModelSignals()
        super().__init__(*args, **kwargs)

    def __setitem__(self, key, value):
        previous = self.get(key)  # Get the existing value.
        super().__setitem__(key, value)  # Set the value.
        if value != previous:  # There is a change.
            self.signals.updated.emit()  # Emit the signal.
            print(self)  # Show the current state.



model = DataModel(
    name="Johnina Smith",
    age=10,
    favorite_icecream="Vanilla",
    disable_details=False,
)


# We store the backups as a simple list.
backups = []


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        layout = QFormLayout()

        # Dictionary to store the form data, with default data.
        self.name = QLineEdit()
        self.name.textChanged.connect(self.handle_name_changed)
        self.age = QSpinBox()
        self.age.setRange(0, 200)
        self.age.valueChanged.connect(self.handle_age_changed)
        self.icecream = QComboBox()
        self.icecream.addItems(
            ["Vanilla", "Strawberry", "Chocolate"]
        )
        self.icecream.currentTextChanged.connect(
            self.handle_icecream_changed
        )

        self.save_btn = QPushButton("Save")
        self.save_btn.pressed.connect(self.handle_save_btn)
        self.restore_btn = QPushButton("Restore")
        self.restore_btn.pressed.connect(self.handle_restore_btn)

        self.disable_details = QCheckBox("Disable details?")
        self.disable_details.toggled.connect(
            self.handle_disable_details
        )

        layout.addRow("Name", self.name)
        layout.addRow("Age", self.age)
        layout.addRow("Favorite Ice cream", self.icecream)
        layout.addWidget(
            self.disable_details
        )  # QCheckBox has it's own label.
        layout.addRow(self.save_btn)
        layout.addRow(self.restore_btn)

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)


        # __init__ ...
        self.update_ui()
        # Hook our UI sync into the model updated signal.
        model.signals.updated.connect(self.update_ui)

    def update_ui(self):
        """Synchronise the UI with the current model state."""
        self.name.setText(model["name"])
        self.age.setValue(model["age"])
        self.icecream.setCurrentText(model["favorite_icecream"])
        self.disable_details.setChecked(model["disable_details"])

        # Enable/disable fields based on the disable_details state.
        # disable_details is True/False, setting setDisabled to True
        # disables the field.
        self.age.setDisabled(model["disable_details"])
        self.icecream.setDisabled(model["disable_details"])


    def handle_name_changed(self, name):
        model["name"] = name


    def handle_age_changed(self, age):
        model["age"] = age

    def handle_icecream_changed(self, icecream):
        model["favorite_icecream"] = icecream

    def handle_disable_details(self, checked):
        model["disable_details"] = checked

    def handle_save_btn(self):
        # Save a copy of the current model (if we don't copy,
        # changes will modify the backup!)
        backups.append(model.copy())
        print("SAVE:", model)
        print("BACKUPS:", len(backups))

    def handle_restore_btn(self):
        # Randomly get a backup.
        if not backups:
            return  # Ignore if empty.
        random.shuffle(backups)
        backup = backups.pop()  # Remove a backup.
        model.update(backup)  # Overwrite the data in the model.
        print("RESTORE:", model)
        print("BACKUPS:", len(backups))


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Below, demonstrating syncing across two windows.

import random
import sys
from collections import UserDict

from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import (
    QApplication,
    QCheckBox,
    QComboBox,
    QFormLayout,
    QLineEdit,
    QMainWindow,
    QPushButton,
    QSpinBox,
    QWidget,
)


class DataModelSignals(QObject):
    # Emit an "updated" signal when a property changes.
    updated = Signal()


class DataModel(UserDict):
    def __init__(self, *args, **kwargs):
        self.signals = DataModelSignals()
        super().__init__(*args, **kwargs)

    def __setitem__(self, key, value):
        previous = self.get(key)  # Get the existing value.
        super().__setitem__(key, value)  # Set the value.
        if value != previous:  # There is a change.
            self.signals.updated.emit()  # Emit the signal.
            print(self)  # Show the current state.


model = DataModel(
    name="Johnina Smith",
    age=10,
    favorite_icecream="Vanilla",
    disable_details=False,
)

# We store the backups as a simple list.
backups = []


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        layout = QFormLayout()

        # Dictionary to store the form data, with default data.
        self.name = QLineEdit()
        self.name.textChanged.connect(self.handle_name_changed)
        self.age = QSpinBox()
        self.age.setRange(0, 200)
        self.age.valueChanged.connect(self.handle_age_changed)
        self.icecream = QComboBox()
        self.icecream.addItems(
            ["Vanilla", "Strawberry", "Chocolate"]
        )
        self.icecream.currentTextChanged.connect(
            self.handle_icecream_changed
        )

        self.save_btn = QPushButton("Save")
        self.save_btn.pressed.connect(self.handle_save_btn)
        self.restore_btn = QPushButton("Restore")
        self.restore_btn.pressed.connect(self.handle_restore_btn)

        self.disable_details = QCheckBox("Disable details?")
        self.disable_details.toggled.connect(
            self.handle_disable_details
        )

        layout.addRow("Name", self.name)
        layout.addRow("Age", self.age)
        layout.addRow("Favorite Ice cream", self.icecream)
        layout.addWidget(
            self.disable_details
        )  # QCheckBox has it's own label.
        layout.addRow(self.save_btn)
        layout.addRow(self.restore_btn)

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        self.update_ui()
        # Hook our UI sync into the model updated signal.
        model.signals.updated.connect(self.update_ui)

    def update_ui(self):
        """Synchronise the UI with the current model state."""
        self.name.setText(model["name"])
        self.age.setValue(model["age"])
        self.icecream.setCurrentText(model["favorite_icecream"])
        self.disable_details.setChecked(model["disable_details"])

        # Enable/disable fields based on the disable_details state.
        # disable_details is True/False, setting setDisabled to True
        # disables the field.
        self.age.setDisabled(model["disable_details"])
        self.icecream.setDisabled(model["disable_details"])

    def handle_name_changed(self, name):
        model["name"] = name

    def handle_age_changed(self, age):
        model["age"] = age

    def handle_icecream_changed(self, icecream):
        model["favorite_icecream"] = icecream

    def handle_disable_details(self, checked):
        model["disable_details"] = checked

    def handle_save_btn(self):
        # Save a copy of the current model (if we don't copy,
        # changes will modify the backup!)
        backups.append(model.copy())
        print("SAVE:", model)
        print("BACKUPS:", len(backups))

    def handle_restore_btn(self):
        # Randomly get a backup.
        if not backups:
            return  # Ignore if empty.
        random.shuffle(backups)
        backup = backups.pop()  # Remove a backup.
        model.update(backup)  # Overwrite the data in the model.
        print("RESTORE:", model)
        print("BACKUPS:", len(backups))


app = QApplication(sys.argv)


window_a = MainWindow()
window_a.show()

window_b = MainWindow()
window_b.show()


app.exec()