I'm trying to link a data from a QTableModel to a QComboBox via a mapper (QDataWidgetMapper) similarly to the Qt example showed here : https://doc.qt.io/qt-5/qtwidgets-itemviews-simplewidgetmapper-example.html
Using the propertyName "currentIndex" or "currentText", I know I can map directly the combobox. But I would like to map a user defined data.
Here is a small example : there is Object with Name and CategoryID, and Categories with ID and Name. The combobox has all the categories possible, but its index should change according to the object's CategoryID.
Here is what I have until now:
TableModel :
class ObjectModel(QAbstractTableModel):
def __init__(self, objects, parent=None):
super(ObjectModel, self).__init__(parent)
self.columns = ['name', 'category']
self.objectList = objects
def rowCount(self, parent=QModelIndex()):
return len(self.objectList)
def columnCount(self, parent=QModelIndex()):
return len(self.columns)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.columns[section].title()
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole or role == Qt.EditRole:
row = self.objectList[index.row()]
column_key = self.columns[index.column()]
return row[column_key]
else:
return None
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
if self.objectList:
column_key = self.columns[index.column()]
self.objectList[index.row()][column_key] = value
self.dataChanged.emit(index, index, [])
return True
return True
StandardItemModel :
class CategoryModel(QStandardItemModel):
def __init__(self, categories, parent=None):
super(CategoryModel, self).__init__(parent)
self.insertColumns(0, 2, QModelIndex())
self.insertRows(0, len(categories), QModelIndex())
self.categoryList = sorted(categories, key=lambda idx: (idx['name']))
for i, category in enumerate(categories):
self.setData(self.index(i, 0, QModelIndex()), category["id"], Qt.UserRole)
self.setData(self.index(i, 1, QModelIndex()), category["name"], Qt.UserRole)
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole or role == Qt.EditRole:
return self.categoryList[index.row()]['name']
if role == Qt.UserRole:
return self.categoryList[index.row()]["id"]
View :
class CustomView(QWidget):
def __init__(self, parent=None):
super(CustomView, self).__init__(parent)
self.mapper = QDataWidgetMapper()
self.spinboxMapperIndex = QSpinBox()
self.lineEdit = QLineEdit()
self.comboBox = QComboBox()
self.spinboxMapperIndex.valueChanged.connect(self.changeMapperIndex)
self.setupLayout()
def setModel(self, model):
self.mapper.setModel(model)
self.mapper.addMapping(self.lineEdit, 0)
self.mapper.addMapping(self.comboBox, 1, b'currentData')
self.mapper.toFirst()
def changeMapperIndex(self, index):
self.mapper.setCurrentIndex(index)
def setupLayout(self):
layout = QVBoxLayout()
layout.addWidget(self.spinboxMapperIndex)
layout.addWidget(self.lineEdit)
layout.addWidget(self.comboBox)
self.setLayout(layout)
Main :
if __name__ == '__main__':
app = QApplication(sys.argv)
categoryList = [
{
"id": 23,
"name": "Flower"
},
{
"id": 456,
"name": "Furniture"
}
]
categoryModel = CategoryModel(categoryList)
objectList = [
{
"name": "Lotus",
"category": 23
},
{
"name": "Table",
"category": 456
}
]
objectModel = ObjectModel(objectList)
view = CustomView()
view.comboBox.setModel(categoryModel)
view.setModel(objectModel)
view.show()
sys.exit(app.exec_())
If I change the ID to a logical increment integer, and I use the currentIndex as propertyName in the mapping process, this is working. But I would like a more generic way if I change the order in the category list.