Dear Lazyweb,
I'm working on a personal PyQt4 project for my parents' office, and I need to
autocomplete an editable QComboBox.
I can't use a standard Qt model (I need to store python objects in it), so I subclassed QAbstractTableModel.
Here it is, it's a fairly generic subclass of QAbstractTableModel, with
variable number of columns:
class QGenericTableModel(QAbstractTableModel): def __init__(self, columns, parent=None, *args): super(QGenericTableModel, self).__init__(parent, *args) self.columns = columns self.headers = {} self.table = [] def rowCount(self, parent=QModelIndex()): return len(self.table) def columnCount(self, parent): return self.columns def row(self, row): return self.table[row] def setData(self, index, value, role): if index.isValid() and role == Qt.EditRole: row = index.row() t = self.table[row] if index.column() >= self.columns: return False if isinstance(value, QVariant): t[index.column()] = value.toString().simplified() else: t[index.column()] = value self.emit(SIGNAL('dataChanged'), index, index) return True return False def data(self, index, role = Qt.DisplayRole): if not index.isValid(): return QVariant() if (index.row() >= len(self.table)) or (index.row() < 0): return QVariant() if role == Qt.DisplayRole: return QVariant(self.table[index.row()][index.column()]) return QVariant() def insertRow(self, row, parent=QModelIndex()): self.insertRows(row, 1, parent) def insertRows(self, row, count, parent=QModelIndex()): self.beginInsertRows(parent, row, row+count-1) for i in xrange(count): self.table.insert(row, ['',]*self.columns) self.endInsertRows() return True def removeRow(self, row, parent=QModelIndex()): self.removeRows(row, 1, parent) def removeRows(self, row, count, parent=QModelIndex()): self.beginRemoveRows(parent, row, row+count-1) for i in reversed(xrange(count)): self.table.pop(row+i) self.endRemoveRows() return True def flags(self, index): if not index.isValid(): return Qt.ItemIsEnabled return super(QGenericTableModel, self).flags(index) | Qt.ItemIsEditable def headerData(self, column, orientation, role = Qt.DisplayRole): if role != Qt.DisplayRole: return QVariant() if orientation == Qt.Horizontal: if column >= self.columns: return QVariant() return self.headers[column] return QVariant() def setHeaderData(self, column, orientation, value, role = Qt.EditRole): self.headers[column] = value
It might not be perfect, but it works, and it's all that matters at the moment
(since it's my first serious PyQt4 project). Oh, well, it worked until I tried
to use it with an editable QComboBox.
And here's the app code (for the full working code, insert the above class right after the imports):
import sys from PyQt4.QtGui import * from PyQt4.QtCore import * app = QApplication(sys.argv) model = QStandardItemModel() for i, word in enumerate(['saluton', 'gxis la revido', 'hello', 'goodbye']): item = QStandardItem(word) model.setItem(i, 0, item) #model = QGenericTableModel(1) #for i, word in enumerate(['saluton', 'gxis la revido', 'hello', 'goodbye']): # model.insertRow(i) # index = model.index(i, 0) # model.setData(index, word, Qt.EditRole) combo = QComboBox() filterModel = QSortFilterProxyModel(combo) completer = QCompleter(combo) filterModel.setFilterCaseSensitivity(Qt.CaseInsensitive) filterModel.setSourceModel(model) filterModel.setFilterKeyColumn(0) completer.setModel(filterModel) completer.setCompletionColumn(0) completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) combo.setEditable(True) combo.setCompleter(completer) combo.setModel(model) combo.setModelColumn(0) if combo.isEditable(): app.connect(combo.lineEdit(), SIGNAL('textEdited(QString)'), filterModel.setFilterFixedString) combo.setModel(model) combo.setModelColumn(0) combo.show() sys.exit(app.exec_())
Now, try running it. It works well when I use a QStandardItemModel.
Then, try decommenting the part where I use my subclassed model: it just doesn't
work: clicking on any item makes the QComboBox not change its currentIndex,
and this only happens if the combobox is editable.
I suspect I'm forgetting to override some function in my model, but I don't know
what exactly to override, and Google didn't help me. This is also backed up by
the fact that the exact same behaviour shows up when, instead of a QComboBox, I
try to autocomplete on a QLineEdit.
So, dear readers: can anyone explain what is happening? Thanks in advance!
UPDATE: it turned out that the culprit was the following bit inside data():
if role == Qt.DisplayRole: return QVariant(self.table[index.row()][index.column()])
Adding also Qt.EditRole to the list of possible choices fixed the bug. YAY!