tristam
8th March 2014, 04:33
I have an application in which I read and write to disk using data from a custom tree model. The model data is mapped to widgets on a standard form UI using QDataWidgetMapper. I noticed that the data reads in perfectly--except for QComboBox widgets. (The model holds the data just fine--it's the widget that doesn't properly display it.) After doing some searching, it seems that these require a custom delegate in order for the editor data to be correctly set from the model. I decided to test this out by developing a very simple form backed by a QAbstractTableModel. I have mostly followed this tutorial: http://doc.qt.digia.com/qq/qq21-datawidgetmapper.html ; my code (Python 2.7.3/PySide 1.2.1) is below:
#!/usr/bin/python
from PySide.QtCore import *
from PySide.QtGui import *
import sys
# Since the delegate only needs to communicate information between the model and the widget mapper,
# we only need to provide implementations of the setEditorData() and setModelData() functions
class CustomDelegate(QAbstractItemDelegate):
def __init__(self, parent=None):
super(CustomDelegate, self).__init__(parent)
def setEditorData(self, editor, index):
if editor.property("currentIndex") is not None:
value = index.data(Qt.DisplayRole)
editor.setProperty("currentIndex", index.data())
return
text = index.model().data(index, Qt.DisplayRole)
editor.setText(text)
def setModelData(self, editor, model, index):
print 'setting model data'
if editor.property("currentIndex") is not None:
return
model.setData(index, editor.currentText(), Qt.EditRole)
class TableModel(QAbstractTableModel):
def __init__(self, data=None, parent=None):
super(TableModel, self).__init__(parent)
if data is not None:
self.tbldata = data
else:
self.tbldata = [[]]
def rowCount(self, parent=QModelIndex()):
return len(self.tbldata)
def columnCount(self, parent=QModelIndex()):
return len(self.tbldata[0])
def data(self, index, role):
if role == Qt.DisplayRole:
return self.tbldata[index.row()][index.column()]
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled
def setData(self, index, value, role):
if role == Qt.EditRole:
self.tbldata[index.row()][index.column()] = value
return True
return False
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.setWindowTitle("Test mapping to QComboBox")
tbldata = [
["Carol", "The Lighthouse", 1],
["Donald", "47338 Park Ave.", 2],
["Emma", "Research Station", 0]
]
self.model = TableModel(tbldata)
self.view = QTableView() # you have a view, but you won't add it as a widget to the layout
self.view.setModel(self.model)
# create widgets
name_label = QLabel("Name:")
address_label = QLabel("Address:")
type_label = QLabel("Type:")
self.name_edit = QLineEdit()
self.address_edit = QLineEdit()
self.type_combo = QComboBox()
self.type_combo.addItems(["0", "1", "2"])
prev_button = QPushButton("Previous")
next_button = QPushButton("Next")
change_button = QPushButton("Change Data")
layout = QGridLayout()
layout.addWidget(name_label, 0, 0)
layout.addWidget(self.name_edit, 0, 1)
layout.addWidget(prev_button, 0, 2)
layout.addWidget(address_label, 1, 0)
layout.addWidget(self.address_edit, 1, 1)
layout.addWidget(next_button, 1, 2)
layout.addWidget(type_label, 2, 0)
layout.addWidget(self.type_combo, 2, 1)
layout.addWidget(change_button, 2, 2)
self.setLayout(layout)
# mapper
self.mapper = QDataWidgetMapper()
self.mapper.setModel(self.model)
self.delegate = CustomDelegate()
self.mapper.setItemDelegate(self.delegate)
self.mapper.addMapping(self.name_edit, 0)
self.mapper.addMapping(self.address_edit, 1)
self.mapper.addMapping(self.type_combo, 2)
self.mapper.setCurrentIndex(0)
# connections
prev_button.clicked.connect(self.goPrevRecord)
next_button.clicked.connect(self.goNextRecord)
change_button.clicked.connect(self.changeData)
def goPrevRecord(self):
self.mapper.toPrevious()
def goNextRecord(self):
self.mapper.toNext()
def changeData(self):
self.model.tbldata = [
["Paul", "72 Main St.", 2],
["John", "473 Wabash Ave.", 2],
["Jenny", "Outer Space", 0]
]
app = QApplication(sys.argv)
form = Form()
form.show()
app.exec_()
Everything runs perfectly -- except that I'm unable to write data back to the model from the widgets! I put a print statement in the setModelData() method of the delegate to see whether the method is ever called, and it doesn't seem to be. And if it's not called, that would certainly explain why no edits go through to the model!
The Trolltech tutorial I linked to makes it a point that all I should need to re-implement are the setEditorData() and setModelData() methods, but it seems as though I am missing something elementary, and a few hours of searching (and continued tinkering) has turned up nothing. If anyone can help point me in the right direction, I would deeply appreciate it!
#!/usr/bin/python
from PySide.QtCore import *
from PySide.QtGui import *
import sys
# Since the delegate only needs to communicate information between the model and the widget mapper,
# we only need to provide implementations of the setEditorData() and setModelData() functions
class CustomDelegate(QAbstractItemDelegate):
def __init__(self, parent=None):
super(CustomDelegate, self).__init__(parent)
def setEditorData(self, editor, index):
if editor.property("currentIndex") is not None:
value = index.data(Qt.DisplayRole)
editor.setProperty("currentIndex", index.data())
return
text = index.model().data(index, Qt.DisplayRole)
editor.setText(text)
def setModelData(self, editor, model, index):
print 'setting model data'
if editor.property("currentIndex") is not None:
return
model.setData(index, editor.currentText(), Qt.EditRole)
class TableModel(QAbstractTableModel):
def __init__(self, data=None, parent=None):
super(TableModel, self).__init__(parent)
if data is not None:
self.tbldata = data
else:
self.tbldata = [[]]
def rowCount(self, parent=QModelIndex()):
return len(self.tbldata)
def columnCount(self, parent=QModelIndex()):
return len(self.tbldata[0])
def data(self, index, role):
if role == Qt.DisplayRole:
return self.tbldata[index.row()][index.column()]
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled
def setData(self, index, value, role):
if role == Qt.EditRole:
self.tbldata[index.row()][index.column()] = value
return True
return False
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.setWindowTitle("Test mapping to QComboBox")
tbldata = [
["Carol", "The Lighthouse", 1],
["Donald", "47338 Park Ave.", 2],
["Emma", "Research Station", 0]
]
self.model = TableModel(tbldata)
self.view = QTableView() # you have a view, but you won't add it as a widget to the layout
self.view.setModel(self.model)
# create widgets
name_label = QLabel("Name:")
address_label = QLabel("Address:")
type_label = QLabel("Type:")
self.name_edit = QLineEdit()
self.address_edit = QLineEdit()
self.type_combo = QComboBox()
self.type_combo.addItems(["0", "1", "2"])
prev_button = QPushButton("Previous")
next_button = QPushButton("Next")
change_button = QPushButton("Change Data")
layout = QGridLayout()
layout.addWidget(name_label, 0, 0)
layout.addWidget(self.name_edit, 0, 1)
layout.addWidget(prev_button, 0, 2)
layout.addWidget(address_label, 1, 0)
layout.addWidget(self.address_edit, 1, 1)
layout.addWidget(next_button, 1, 2)
layout.addWidget(type_label, 2, 0)
layout.addWidget(self.type_combo, 2, 1)
layout.addWidget(change_button, 2, 2)
self.setLayout(layout)
# mapper
self.mapper = QDataWidgetMapper()
self.mapper.setModel(self.model)
self.delegate = CustomDelegate()
self.mapper.setItemDelegate(self.delegate)
self.mapper.addMapping(self.name_edit, 0)
self.mapper.addMapping(self.address_edit, 1)
self.mapper.addMapping(self.type_combo, 2)
self.mapper.setCurrentIndex(0)
# connections
prev_button.clicked.connect(self.goPrevRecord)
next_button.clicked.connect(self.goNextRecord)
change_button.clicked.connect(self.changeData)
def goPrevRecord(self):
self.mapper.toPrevious()
def goNextRecord(self):
self.mapper.toNext()
def changeData(self):
self.model.tbldata = [
["Paul", "72 Main St.", 2],
["John", "473 Wabash Ave.", 2],
["Jenny", "Outer Space", 0]
]
app = QApplication(sys.argv)
form = Form()
form.show()
app.exec_()
Everything runs perfectly -- except that I'm unable to write data back to the model from the widgets! I put a print statement in the setModelData() method of the delegate to see whether the method is ever called, and it doesn't seem to be. And if it's not called, that would certainly explain why no edits go through to the model!
The Trolltech tutorial I linked to makes it a point that all I should need to re-implement are the setEditorData() and setModelData() methods, but it seems as though I am missing something elementary, and a few hours of searching (and continued tinkering) has turned up nothing. If anyone can help point me in the right direction, I would deeply appreciate it!