问题
I am displaying data from an SQLite database in a QTableView using a QSqlTableModel. Letting the user edit this data works fine. However, for some columns I want to use QComboboxes instead of free text cells, to restrict the list of possible answers.
I have found this SO answer and am trying to implement it on my model/view setting, but I'm running into problems (so this is a follow-up).
Here's a full mini-example:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PyQt5 import QtSql
from PyQt5.QtWidgets import (QWidget, QTableView, QApplication, QHBoxLayout,
QItemDelegate, QComboBox)
from PyQt5.QtCore import pyqtSlot
import sys
class ComboDelegate(QItemDelegate):
"""
A delegate that places a fully functioning QComboBox in every
cell of the column to which it's applied
source: https://gist.github.com/Riateche/5984815
"""
def __init__(self, parent, items):
self.items = items
QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
combo = QComboBox(parent)
li = []
for item in self.items:
li.append(item)
combo.addItems(li)
combo.currentIndexChanged.connect(self.currentIndexChanged)
return combo
def setEditorData(self, editor, index):
editor.blockSignals(True)
# editor.setCurrentIndex(int(index.model().data(index))) #from original code
editor.setCurrentIndex(index.row()) # replacement
editor.blockSignals(False)
def setModelData(self, editor, model, index):
model.setData(index, editor.currentIndex())
@pyqtSlot()
def currentIndexChanged(self):
self.commitData.emit(self.sender())
class Example(QWidget):
def __init__(self):
super().__init__()
self.resize(400, 150)
self.createConnection()
self.fillTable() # comment out to skip re-creating the SQL table
self.createModel()
self.initUI()
def createConnection(self):
self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
self.db.setDatabaseName("test.db")
if not self.db.open():
print("Cannot establish a database connection")
return False
def fillTable(self):
self.db.transaction()
q = QtSql.QSqlQuery()
q.exec_("DROP TABLE IF EXISTS Cars;")
q.exec_("CREATE TABLE Cars (Company TEXT, Model TEXT, Year NUMBER);")
q.exec_("INSERT INTO Cars VALUES ('Honda', 'Civic', 2009);")
q.exec_("INSERT INTO Cars VALUES ('VW', 'Golf', 2013);")
q.exec_("INSERT INTO Cars VALUES ('VW', 'Polo', 1999);")
self.db.commit()
def createModel(self):
self.model = QtSql.QSqlTableModel()
self.model.setTable("Cars")
self.model.select()
def initUI(self):
layout = QHBoxLayout()
self.setLayout(layout)
view = QTableView()
layout.addWidget(view)
view.setModel(self.model)
view.setItemDelegateForColumn(0, ComboDelegate(self, ["VW", "Honda"]))
for row in range(0, self.model.rowCount()):
view.openPersistentEditor(self.model.index(row, 0))
def closeEvent(self, e):
for row in range(self.model.rowCount()):
print("row {}: company = {}".format(row, self.model.data(self.model.index(row, 0))))
if (self.db.open()):
self.db.close()
def main():
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
In this case, I want to use a QCombobox on the "Company" column. It should be displayed all the time, so I'm calling openPersistentEditor.
Problem 1: default values I would expect that this shows the non-edited field's content when not edited (i.e. the company as it is listed in the model), but instead it apparently shows the ith element of the combobox's choices. How can I make each combobox show the model's actual content for this field by default?
Problem 2: editing When you comment out "self.fill_table()" you can check whether the edits arrive in the SQL database. I would expect that choosing any field in the dropdown list would replace the original value. But (a) I have to make every choice twice (the first time, the value displayed in the cell remains the same), and (b) the data appears in the model weirdly (changing the first column to 'VW', 'Honda', 'Honda' results in ('1', 'VW', '1' in the model). I think this is because the code uses editor.currentIndex() in the delegate's setModelData, but I have not found a way to use the editor's content instead. How can I make the code report the user's choices correctly back to the model? (And how do I make this work on first click, instead of needing 2 clicks?)
Any help greatly appreciated. (I have read the documentation on QAbstractItemDelegate, but I don't find it particularly helpful.)
回答1:
Found the solution with the help of the book Rapid GUI Programming with Python and Qt:
createEditor
and setEditorData
do not work as I expected (I was misguided because the example code looked like it was using the text content but instead was dealing with index numbers). Instead, they should look like this:
def setEditorData(self, editor, index):
editor.blockSignals(True)
text = index.model().data(index, Qt.DisplayRole)
try:
i = self.items.index(text)
except ValueError:
i = 0
editor.setCurrentIndex(i)
editor.blockSignals(False)
def setModelData(self, editor, model, index):
model.setData(index, editor.currentText())
I hope this helps someone down the line.
来源:https://stackoverflow.com/questions/51945016/pyqt-qcombobox-in-qtableview