import random import sys from collections import UserDict from PyQt5.QtCore import QObject, pyqtSignal, Qt from PyQt5.QtWidgets import QMainWindow, QApplication, QFormLayout, QLineEdit, QSpinBox, QComboBox, QPushButton, \ QCheckBox, QWidget class DataModelSignals(QObject): # Emit an "updated" signal when a property changes. updated = pyqtSignal() 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) 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, ) class Controller: """ Simple controller, which handles backups and other operations. """ backups = [] def capitalize(self): model["name"] = model["name"].upper() def store_backup(self): self.backups.append(model.copy()) def restore_backup(self): if not self.backups: return random.shuffle(self.backups) backup = self.backups.pop() model.update(backup) # Overwrite the data in the model print("RESTORE:", model) print("BACKUPS:", len(self.backups)) def apply_title_case(self): model["name"] = model["name"].title() controller = Controller() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("MVC Example") layout = QFormLayout() # Dictionary to store the form data, with default data. self.name = QLineEdit() self.name.textChanged.connect(self.on_name_changed) self.age = QSpinBox() self.age.setRange(0, 200) self.age.valueChanged.connect(self.on_age_changed) self.icecream = QComboBox() self.icecream.addItems(["Vanilla", "Strawberry", "Chocolate"]) self.icecream.currentTextChanged.connect(self.on_icecream_changed) self.title_btn = QPushButton("Set Title Case") self.title_btn.pressed.connect(controller.apply_title_case) # tag::connect[] self.save_btn = QPushButton("Save") self.save_btn.pressed.connect(controller.store_backup) self.restore_btn = QPushButton("Restore") self.restore_btn.pressed.connect(controller.restore_backup) # end::connect[] self.disable_details = QCheckBox("Disable Details?") self.disable_details.toggled.connect(self.on_disable_details_toggled) layout.addRow("Name", self.name) layout.addRow(self.title_btn) layout.addRow("Age", self.age) layout.addRow("Favorite Ice Cream", self.icecream) layout.addWidget(self.disable_details) # QCheckBox has its own label. layout.addRow(self.save_btn) layout.addRow(self.restore_btn) layout.setLabelAlignment(Qt.AlignLeft) 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): self.name.setText(model["name"]) self.age.setValue(model["age"]) self.icecream.setCurrentText(model["favorite_icecream"]) self.disable_details.setChecked(model["disable_details"]) self.age.setDisabled(model["disable_details"]) self.icecream.setDisabled(model["disable_details"]) def on_name_changed(self, name): model["name"] = name def on_age_changed(self, age): model["age"] = age def on_icecream_changed(self, icecream): model["favorite_icecream"] = icecream def on_disable_details_toggled(self, checked): model["disable_details"] = checked if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())