Hi @Michal_Plichta welcome to the forum & congratulations on reaching that point in your project where you realize you should have started with MVC all along. It happens to us all
As you probably know, Qt comes with a bunch of built in “model views” which can be use to display data in lists, dropdowns and tables. But for general UI use you’re on your own. I’ll cover both cases below.
Standard UI MVC
Use a dictionary to store the actual data. You can derive this from UserDict
to define some custom behaviors – for example, adding signals to fire any time a value is updated.
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) # 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,
)
Then in your UI you receive signals from widgets that indicate they are updated & and use model['name_of_value']
to set the new value. You can do clever things here, like using the objectName
to link to the key in the dictionary if you’re so inclined.
The model will only emit the updated signal if the value has changed. You connect this .updated
signal to your UI, through an def update_ui(self)
method. This reads the values from the model & applies them to the UI – all of them, because setting the same value that is already shown has no cost.
That way you can hold all your UI update logic in a single place, making it easy to add complex logic (like disabling/enabling bits of hte UI depending on config state).
The more complicated stuff
To show the table I would go with a QTableView
and use a combobox delegate as the editor component for the cell. That allows you to customize what is shown in the dropdown and still benefit from using a model to store the data.
Something like the following
import sys
from PySide6 import QtCore, QtWidgets
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QComboBox, QItemDelegate
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def data(self, index, role):
if role == Qt.ItemDataRole.DisplayRole:
value = self._data[index.row()][index.column()]
return str(value)
if role == Qt.ItemDataRole.EditRole:
value = self._data[index.row()][index.column()]
return value
def setData(self, index, value, role):
if role == Qt.ItemDataRole.EditRole:
self._data[index.row()][index.column()] = value
return True
return False
def flags(self, index):
flags = (
Qt.ItemFlag.ItemIsEnabled
| Qt.ItemFlag.ItemIsSelectable
| Qt.ItemFlag.ItemIsEditable
)
return flags
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
return len(self._data[0])
class ComboDelegate(QItemDelegate):
"""
Add a QComboBox in every cell of the table.
"""
def __init__(self, parent, values):
super(ComboDelegate, self).__init__(parent)
self.model = None
self.values = list(set(values)) # Unique values.
def createEditor(self, parent, option, index):
combobox = QComboBox(parent)
combobox.addItems(self.values)
return combobox
def setEditorData(self, editor, index):
text = index.model().data(index, Qt.EditRole)
print("Update the editor from the model:", text)
if index:
editor.setCurrentText(text)
def setModelData(self, editor, model, index):
print("Updating the model model:", editor, model, index, editor.currentText())
model.setData(index, editor.currentText(), Qt.ItemDataRole.EditRole)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
combovalues = ["", "HOT_MIC_SW", "IFF_M4_CODE_SW", "IFF_M4_REPLY_SW"]
self.table = QtWidgets.QTableView()
self.setCentralWidget(self.table)
combodelegate = ComboDelegate(self, combovalues)
self.table.setItemDelegate(combodelegate)
self.model = TableModel(
[
["", "", "", "", ""],
["", "", "", "", ""],
["", "", "", "", ""],
["", "", "", "", ""],
]
)
self.table.setModel(self.model)
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Which gives you a window like this, where each cell can be selected using a combo dropdown.
For the additional details I would add a sidebar, with the options etc. shown in it. This can react to the change in currently selected cell in the view & the current value in the dropdown.
In case it isn’t obvious you can store this information in the same model as that used for the table view — in the example above the model is a list of lists containing strings (from the selected values). But the model can contain anything, as the “values” e.g. dictionaries, objects, etc. and you can store your data however you like inside that. All you need to ensure is that the model can read/write to the value to display in the table, handled through the data
/setData
methods.
I hope this helps. Let me know if you have any questions, I realise it’s pretty complicated stuff.