有一个工作的 QSqlRelationalDelegate 和 QSortFilterProxyModel
Having A Working QSqlRelationalDelegate With QSortFilterProxyModel
我一直在使用 QSortFilterProxyModels
。但是,如果在源模型上设置了 QSqlRelation
,并在视图上设置了 QSqlRelationalDelegate
,那么只要视图切换到代理模型,QSqlRelationalDelegate
就会消失,留下基本的 QLineEdit
或 QSpinBox
.
如何使视图中的列与 QSortFilterProxyModel
和 QSqlRelationalDelegate
一起使用,并提供预期的 QCombobox
下拉菜单?
默认情况下,QSqlRelationalDelegate 无法处理代理模型,因此您必须对其进行子类化。以下可能远非完美,因此欢迎 comments/tweaks,但在混合了 QSqlRelations/straight 数据的视图上一直运行良好,没有故障。
class ProxyDelegate(QSqlRelationalDelegate):
def __init__(self):
QSqlRelationalDelegate.__init__(self)
def createEditor(self, p, o, i): # parent, option, index
if i.model().sourceModel().relation(i.column()).isValid(): # if the column has a QSqlRelation, then make the expected QComboBox
e = QComboBox(p)
return e
else:
return QStyledItemDelegate(p).createEditor(p, o, i)
def setEditorData(self, e, i):
m = i.model()
sM = m.sourceModel()
relation = sM.relation(i.column())
if relation.isValid():
m = i.model()
sM = m.sourceModel()
relation = sM.relation(i.column())
pModel = QSqlTableModel() # pModel means populate model. Because I've aimed for generic use, it makes a new QSqlTableModel, even if one already exists elsewhere for that SQL table
pModel.setTable(relation.tableName())
pModel.select()
e.setModel(pModel)
pModel.sort(pModel.fieldIndex(relation.displayColumn()), Qt.AscendingOrder) # default sorting. A custom attribute would need adding to each source model class, in order for this line to know the desired sorting order for this QComboBox delegate
e.setModelColumn(pModel.fieldIndex(relation.displayColumn()))
e.setCurrentIndex(e.findText(m.data(i).toString()))
else:
return QStyledItemDelegate().setEditorData(e, i)
def setModelData(self, e, m, i):
m = i.model() # this could probably be written more elegantly so you don't need to create another SqlModel
sM = m.sourceModel()
relation = sM.relation(i.column())
table = relation.tableName()
indexColumn = relation.indexColumn()
indexColumnId = sM.fieldIndex(indexColumn)
displayColumn = relation.displayColumn()
if relation.isValid():
pModel = QSqlTableModel()
pModel.setTable(relation.tableName())
pModel.select()
displayColumnId = pModel.fieldIndex(displayColumn)
chosenRowInPModel = pModel.match(pModel.index(0, displayColumnId), Qt.DisplayRole, e.currentText())[0].row()
chosenIdInPModel = pModel.data(pModel.index(chosenRowInPModel, indexColumnId)).toString()
m.setData(i, chosenIdInPModel)
self.closeEditor.emit(e, QAbstractItemDelegate.NoHint)
else:
QStyledItemDelegate().setModelData(e, m, i)
这是一个更好的方法:。
需要使用 mapToSource,因为视图索引可能与模型索引不同。
class Delegate(QtSql.QSqlRelationalDelegate):
"""
Delegate handles custom editing. This allows the user to have good
editing experience.
Because the join table uses a proxy model a subclass QSqlRelationalDelegate
is required. This is to support the foreign key combobox.
"""
def __init__(self, parent = None):
"""
Class constructor.
"""
# Python super lets you avoid referring to the base class explicitly.
super(Delegate, self).__init__(parent)
def createEditor(self, parent, option, index):
"""
This creates the editors in the delegate.
Reimplemented from QAbstractItemDelegate::createEditor().
Returns the widget used to edit the item specified by
index for editing.
The parent widget and style option are used to control how the
editor widget appears.
1. Get the model associated with the view. In this case it is the
QSortFilterProxyModel.
2. Because with a proxy model the views index does not have to be the
same as the models index. If one sorts,
then the index are not the same.
3. mapToSource.
This is why mapToSource is being used.
mapToSouce Reimplement this function to return the
model index in the proxy model that corresponds to the
sourceIndex from the source model.
4. Return the createEditor with the base index being set to the source
model and not the proxy model.
"""
if index.column() == 2:
proxy = index.model()
base_index = proxy.mapToSource(index)
return super(Delegate, self).createEditor(parent, option, base_index)
else:
return super(Delegate, self).createEditor(parent, option, index)
def setEditorData(self, editor, index):
"""
Once the editor has been created and given to the view
the view calls setEditorData().
This gives the delegate the opportunity to populate the editor
with the current data, ready for the user to edit.
Sets the contents of the given editor to the data for the item
at the given index.
Note that the index contains information about the model being used.
The base implementation does nothing.
If you want custom editing you will need to reimplement this function.
1. Get the model which is a QSortFilterProxyModel.
2. Call mapToSource().
Because with a proxy model the views index does not have to be the
same as the models index. If one sorts,
then the index are not the same.
This is why mapToSource is being used. MapToSouce Reimplement this
function to return the model index in the proxy model
that corresponds to the sourceIndex from the source model.
3. Return setEditorData with the editor and the mapToSource index.
4. Else for all other columns return the base method.
"""
if index.column() == 2:
proxy = index.model()
base_index = proxy.mapToSource(index)
return super(JoinDelegate, self).setEditorData(editor, base_index)
else:
return super(Delegate, self).setEditorData(editor, index)
def setModelData(self, editor, model, index):
if index.column() == 2:
base_model = model.sourceModel()
base_index = model.mapToSource(index)
return super(JoinDelegate, self).setModelData(editor, base_model, base_index)
else:
super(Delegate, self).setModelData(editor, model, index)
def sizeHint(self, option, index):
"""
This pure abstract function must be reimplemented if you want to
provide custom rendering. The options are specified by option and
the model item by index.
"""
if index.isValid():
column = index.column()
text = index.model().data(index)
document = QtGui.QTextDocument()
document.setDefaultFont(option.font)
# change cell Width, height (One can add or subtract to change the relative dimension)
return QtCore.QSize(QtSql.QSqlRelationalDelegate.sizeHint(self, option, index).width() - 200,
QtSql.QSqlRelationalDelegate.sizeHint(self, option, index).height() + 40)
else:
return super(Delegate, self).sizeHint(option, index)
我一直在使用 QSortFilterProxyModels
。但是,如果在源模型上设置了 QSqlRelation
,并在视图上设置了 QSqlRelationalDelegate
,那么只要视图切换到代理模型,QSqlRelationalDelegate
就会消失,留下基本的 QLineEdit
或 QSpinBox
.
如何使视图中的列与 QSortFilterProxyModel
和 QSqlRelationalDelegate
一起使用,并提供预期的 QCombobox
下拉菜单?
默认情况下,QSqlRelationalDelegate 无法处理代理模型,因此您必须对其进行子类化。以下可能远非完美,因此欢迎 comments/tweaks,但在混合了 QSqlRelations/straight 数据的视图上一直运行良好,没有故障。
class ProxyDelegate(QSqlRelationalDelegate):
def __init__(self):
QSqlRelationalDelegate.__init__(self)
def createEditor(self, p, o, i): # parent, option, index
if i.model().sourceModel().relation(i.column()).isValid(): # if the column has a QSqlRelation, then make the expected QComboBox
e = QComboBox(p)
return e
else:
return QStyledItemDelegate(p).createEditor(p, o, i)
def setEditorData(self, e, i):
m = i.model()
sM = m.sourceModel()
relation = sM.relation(i.column())
if relation.isValid():
m = i.model()
sM = m.sourceModel()
relation = sM.relation(i.column())
pModel = QSqlTableModel() # pModel means populate model. Because I've aimed for generic use, it makes a new QSqlTableModel, even if one already exists elsewhere for that SQL table
pModel.setTable(relation.tableName())
pModel.select()
e.setModel(pModel)
pModel.sort(pModel.fieldIndex(relation.displayColumn()), Qt.AscendingOrder) # default sorting. A custom attribute would need adding to each source model class, in order for this line to know the desired sorting order for this QComboBox delegate
e.setModelColumn(pModel.fieldIndex(relation.displayColumn()))
e.setCurrentIndex(e.findText(m.data(i).toString()))
else:
return QStyledItemDelegate().setEditorData(e, i)
def setModelData(self, e, m, i):
m = i.model() # this could probably be written more elegantly so you don't need to create another SqlModel
sM = m.sourceModel()
relation = sM.relation(i.column())
table = relation.tableName()
indexColumn = relation.indexColumn()
indexColumnId = sM.fieldIndex(indexColumn)
displayColumn = relation.displayColumn()
if relation.isValid():
pModel = QSqlTableModel()
pModel.setTable(relation.tableName())
pModel.select()
displayColumnId = pModel.fieldIndex(displayColumn)
chosenRowInPModel = pModel.match(pModel.index(0, displayColumnId), Qt.DisplayRole, e.currentText())[0].row()
chosenIdInPModel = pModel.data(pModel.index(chosenRowInPModel, indexColumnId)).toString()
m.setData(i, chosenIdInPModel)
self.closeEditor.emit(e, QAbstractItemDelegate.NoHint)
else:
QStyledItemDelegate().setModelData(e, m, i)
这是一个更好的方法:
需要使用 mapToSource,因为视图索引可能与模型索引不同。
class Delegate(QtSql.QSqlRelationalDelegate):
"""
Delegate handles custom editing. This allows the user to have good
editing experience.
Because the join table uses a proxy model a subclass QSqlRelationalDelegate
is required. This is to support the foreign key combobox.
"""
def __init__(self, parent = None):
"""
Class constructor.
"""
# Python super lets you avoid referring to the base class explicitly.
super(Delegate, self).__init__(parent)
def createEditor(self, parent, option, index):
"""
This creates the editors in the delegate.
Reimplemented from QAbstractItemDelegate::createEditor().
Returns the widget used to edit the item specified by
index for editing.
The parent widget and style option are used to control how the
editor widget appears.
1. Get the model associated with the view. In this case it is the
QSortFilterProxyModel.
2. Because with a proxy model the views index does not have to be the
same as the models index. If one sorts,
then the index are not the same.
3. mapToSource.
This is why mapToSource is being used.
mapToSouce Reimplement this function to return the
model index in the proxy model that corresponds to the
sourceIndex from the source model.
4. Return the createEditor with the base index being set to the source
model and not the proxy model.
"""
if index.column() == 2:
proxy = index.model()
base_index = proxy.mapToSource(index)
return super(Delegate, self).createEditor(parent, option, base_index)
else:
return super(Delegate, self).createEditor(parent, option, index)
def setEditorData(self, editor, index):
"""
Once the editor has been created and given to the view
the view calls setEditorData().
This gives the delegate the opportunity to populate the editor
with the current data, ready for the user to edit.
Sets the contents of the given editor to the data for the item
at the given index.
Note that the index contains information about the model being used.
The base implementation does nothing.
If you want custom editing you will need to reimplement this function.
1. Get the model which is a QSortFilterProxyModel.
2. Call mapToSource().
Because with a proxy model the views index does not have to be the
same as the models index. If one sorts,
then the index are not the same.
This is why mapToSource is being used. MapToSouce Reimplement this
function to return the model index in the proxy model
that corresponds to the sourceIndex from the source model.
3. Return setEditorData with the editor and the mapToSource index.
4. Else for all other columns return the base method.
"""
if index.column() == 2:
proxy = index.model()
base_index = proxy.mapToSource(index)
return super(JoinDelegate, self).setEditorData(editor, base_index)
else:
return super(Delegate, self).setEditorData(editor, index)
def setModelData(self, editor, model, index):
if index.column() == 2:
base_model = model.sourceModel()
base_index = model.mapToSource(index)
return super(JoinDelegate, self).setModelData(editor, base_model, base_index)
else:
super(Delegate, self).setModelData(editor, model, index)
def sizeHint(self, option, index):
"""
This pure abstract function must be reimplemented if you want to
provide custom rendering. The options are specified by option and
the model item by index.
"""
if index.isValid():
column = index.column()
text = index.model().data(index)
document = QtGui.QTextDocument()
document.setDefaultFont(option.font)
# change cell Width, height (One can add or subtract to change the relative dimension)
return QtCore.QSize(QtSql.QSqlRelationalDelegate.sizeHint(self, option, index).width() - 200,
QtSql.QSqlRelationalDelegate.sizeHint(self, option, index).height() + 40)
else:
return super(Delegate, self).sizeHint(option, index)