How To Customize QTableView's Editing BehaviorVirgil Dupras2009-12-28
QTableView's default behaviorThe biggest problem with Additionally, Tab and Backtab navigation is quite funky. It continues editing even when reaching the end of a line (it starts editing the line below). More annoying, it doesn't step over cells that are not editable. Thus, if you tab next to a cell that is not editable, editing will stop right there, even if there's an editable cell right of that non-editable cell. Last, even though How to fix itIn moneyGuru, I want rows to be editable by pressing Return or cell double-clicking. I want the edits to be row-based, that is, that a row's edits is buffered until the user either commits those edits by pressing Return again, clicks away or tabs until the end of the row. This means that whenever editing ends in any other way other than the user pressing Escape, the model's To obtain that behavior, I had to subclass After some messing around, it turns our that it's much easier to obtain that behavior by overriding For the editing start position problem, the best solution I found is to simply override Then, all you need are helper methods to find first/next/previous editable index from a base index. Code speaks better than words: class TableView(QTableView): def _firstEditableIndex(self, originalIndex, columnIndexes=None): """Returns the first editable index in `originalIndex`'s row or None. If `columnIndexes` is not None, the scan for an editable index will be limited to these columns. """ model = self.model() h = self.horizontalHeader() editedRow = originalIndex.row() if columnIndexes is None: # We use logicalIndex() because it's possible the columns have been # re-ordered. columnIndexes = [h.logicalIndex(i) for i in range(h.count())] create = lambda col: model.createIndex(editedRow, col, None) scannedIndexes = [create(i) for i in columnIndexes if not h.isSectionHidden(i)] isEditable = lambda index: model.flags(index) & Qt.ItemIsEditable editableIndexes = filter(isEditable, scannedIndexes) return editableIndexes[0] if editableIndexes else None def _previousEditableIndex(self, originalIndex): """Returns the first editable index at the left of `originalIndex` or None. """ h = self.horizontalHeader() myCol = originalIndex.column() columnIndexes = [h.logicalIndex(i) for i in range(h.count())] # keep only columns before myCol columnIndexes = columnIndexes[:columnIndexes.index(myCol)] # We want the previous item, the columns have to be in reverse order columnIndexes = reversed(columnIndexes) return self._firstEditableIndex(originalIndex, columnIndexes) def _nextEditableIndex(self, originalIndex): """Returns the first editable index at the right of `originalIndex` or None. """ h = self.horizontalHeader() myCol = originalIndex.column() columnIndexes = [h.logicalIndex(i) for i in range(h.count())] # keep only columns after myCol columnIndexes = columnIndexes[columnIndexes.index(myCol)+1:] return self._firstEditableIndex(originalIndex, columnIndexes) def closeEditor(self, editor, hint): # The problem we're trying to solve here is the edit-and-go-away problem. # When ending the editing with submit or return, there's no problem, the # model's submit()/revert() is correctly called. However, when ending # editing by clicking away, submit() is never called. Fortunately, # closeEditor is called and, AFAIK, it's the only case where it's called # with NoHint (0). So, in these cases, we want to call model.submit() if hint == QAbstractItemDelegate.NoHint: QTableView.closeEditor(self, editor, QAbstractItemDelegate.SubmitModelCache) # And here, what we're trying to solve is the problem with editing # next/previous lines. If there are no more editable indexes, stop # editing right there. Additionally, we are making tabbing step over # non-editable cells elif hint in (QAbstractItemDelegate.EditNextItem, QAbstractItemDelegate.EditPreviousItem): if hint == QAbstractItemDelegate.EditNextItem: editableIndex = self._nextEditableIndex(self.currentIndex()) else: editableIndex = self._previousEditableIndex(self.currentIndex()) if editableIndex is None: QTableView.closeEditor(self, editor, QAbstractItemDelegate.SubmitModelCache) else: QTableView.closeEditor(self, editor, 0) self.setCurrentIndex(editableIndex) self.edit(editableIndex) else: QTableView.closeEditor(self, editor, hint) def keyPressEvent(self, event): if (event.key() == Qt.Key_Return) and \ (self.state() != QAbstractItemView.EditingState): selectedRows = self.selectionModel().selectedRows() if selectedRows: selectedIndex = selectedRows[0] editableIndex = self._firstEditableIndex(selectedIndex) self.setCurrentIndex(editableIndex) self.edit(editableIndex) else: QTableView.keyPressEvent(self, event) |
|
|
This site is best viewed with Opera while listening to The White Stripes |
|