Unsolved Programmatically selecting next/prev row in QTableView skips rows
-
While the up/down arrow key navigation defined in QAbstractItemView correctly navigate through the rows, the following code triggered from QAction skips rows which have the same value in column 0 when moving to the previous row (up) but not the next row (down). I haven't had much success debugging the model code who sorts by that same column zero.
def selectNextEvent(self): indexes = self.selectionModel().selectedIndexes() if indexes: index = self.model().index(indexes[0].row() + 1, indexes[0].column()) else: index = self.model().index(0, 0) self.selectionModel().select(index, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) def selectPrevEvent(self): indexes = self.selectionModel().selectedIndexes() if indexes: index = self.model().index(indexes[0].row() - 1, indexes[0].column()) else: index = self.model().index(self.model().rowCount()-1, 0) self.selectionModel().select(index, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
Thoughts on where to start here?
Thanks!
-
Hi,
I'd start by a boundary check. You allow your index to go over the last element and prior to the first element in your model.
-
@SGaist I am having trouble following. Would you mind rephrasing your reply? Thank you!
-
You don't check wether your rows calculation stay between zero and model' row count - 1.
-
That is correct. Although the problem I am having is in the middle of the range of rows.
-
I don't remember whether the selected indexes are ordered, did you check that ?
-
@SGaist the table view is set to only select a single, entire row at a time. So that shouldn’t matter.
-
@SGaist what is very curious is that it only skips rows that have the same value in the sort column, and only when going from a high value to a low value. That tells me that it could have something to do with my model code. But I am not sure what to check there.
-
@patrickkidd Here is my model code:
class EventModel(QAbstractTableModel, util.Debug): DATE = 'Date' DESCRIPTION = 'Description' PERSON = 'Person' LOGGED = 'Logged' SYMPTOM = 'Δ Symptom' ANXIETY = 'Anxiety' RELATIONSHIP = 'Relationship' FUNCTIONING = 'Functioning' HEADERS = [DATE, DESCRIPTION, PERSON, LOGGED, SYMPTOM, ANXIETY, RELATIONSHIP, FUNCTIONING] def __init__(self, parent=None): super().__init__(parent) self.source = None self.events = [] def init(self, source): self.beginResetModel() self.source = source self.source.eventAdded.connect(self.onEventAdded) self.source.eventChanged.connect(self.onEventChanged) self.source.eventRemoved.connect(self.onEventRemoved) self.events = self.source.events() self.endResetModel() def deinit(self): self.beginResetModel() self.source.eventAdded.disconnect(self.onEventAdded) self.source.eventChanged.disconnect(self.onEventChanged) self.source.eventRemoved.disconnect(self.onEventRemoved) self.source = None self.events = [] self.endResetModel() def index(self, row, col, parent=QModelIndex()): if self.hasIndex(row, col, parent): return self.createIndex(row, col) else: return QModelIndex() def rowCount(self, index=QModelIndex()): return len(self.events) def columnCount(self, index=QModelIndex()): return len(self.HEADERS) def headerData(self, section, orientation, role=Qt.DisplayRole): if orientation == Qt.Horizontal: if role == Qt.DisplayRole: return self.HEADERS[section] return QVariant() def isColumn(self, index, labels): if not isinstance(labels, list): labels = [labels] for label in labels: if self.HEADERS[index.column()] == label: return True return False def data(self, index, role): if role in [Qt.DisplayRole, Qt.EditRole]: event = self.events[index.row()] if self.isColumn(index, self.PERSON): return event.parentName() elif self.isColumn(index, self.DATE): return util.dateString(event.date()) elif self.isColumn(index, self.DESCRIPTION): return event.description() elif self.isColumn(index, self.LOGGED): return util.dateString(event.loggedDate()) elif self.isColumn(index, self.SYMPTOM): return event.symptom() elif self.isColumn(index, self.ANXIETY): return event.anxiety() elif self.isColumn(index, self.FUNCTIONING): return event.functioning() elif self.isColumn(index, self.RELATIONSHIP): return event.relationship() elif role == Qt.BackgroundRole: event = self.events[index.row()] if event.nodal(): return QBrush(util.NODAL_COLOR) elif role == Qt.UserRole: if self.isColumn(index, self.DATE): event = self.events[index.row()] return event.date() elif self.isColumn(index, self.PERSON): event = self.events[index.row()] if event.parent().__class__.__name__ != 'Scene': return event.parent().id else: return None return QVariant() def setData(self, index, value, role=Qt.EditRole): if role == Qt.EditRole: event = self.events[index.row()] if self.isColumn(index, self.PERSON): person = self.source.find(id=value) event.setParent(person) elif self.isColumn(index, self.DATE): event.setDate(value) elif self.isColumn(index, self.DESCRIPTION): event.setDescription(value) elif self.isColumn(index, self.SYMPTOM): event.setSymptom(value) elif self.isColumn(index, self.ANXIETY): event.setAnxiety(value) elif self.isColumn(index, self.FUNCTIONING): event.setFunctioning(value) elif self.isColumn(index, self.RELATIONSHIP): event.setRelationship(value) else: return False else: return False self.dataChanged.emit(index, index) return True def flags(self, index): event = self.events[index.row()] if event.readOnly(): if (self.source.__class__.__name__ == 'Scene' and event == self.source.nowEvent) or \ self.isColumn(index, [self.DESCRIPTION, self.PERSON, self.LOGGED]): return super().flags(index) else: return super().flags(index) | Qt.ItemIsEditable else: if self.isColumn(index, [self.DATE, self.DESCRIPTION, self.PERSON, self.SYMPTOM, self.ANXIETY, self.FUNCTIONING, self.RELATIONSHIP]): return super().flags(index) | Qt.ItemIsEditable else: return super().flags(index) return super().flags(index) def sort(self, column, order=Qt.AscendingOrder): self.layoutAboutToBeChanged.emit() self.events = sorted(self.events) self.layoutChanged.emit() def eventForIndex(self, index): return self.events[index.row()] def indexForEvent(self, event): row = self.events.index(event) return self.createIndex(row, 0) def eventForRow(self, row): return self.events[row] def onEventAdded(self, event): if event in self.events: # bug in Scene return row = self.source.events().index(event) self.beginInsertRows(QModelIndex(), row, row) self.events.insert(row, event) self.endInsertRows() def onEventRemoved(self, event): if not event in self.events: # bug in Scene return row = self.events.index(event) self.beginRemoveRows(QModelIndex(), row, row) self.events.remove(event) self.endRemoveRows() def onEventChanged(self, event, property): row = self.events.index(event) col = None if property.name() == 'date': col = self.HEADERS.index(self.DATE) self.sort(col) elif property.name() == 'description': col = self.HEADERS.index(self.DESCRIPTION) elif property.name() == 'parentName': col = self.HEADERS.index(self.PERSON) elif property.name() == 'symptom': col = self.HEADERS.index(self.SYMPTOM) elif property.name() == 'anxiety': col = self.HEADERS.index(self.ANXIETY) elif property.name() == 'functioning': col = self.HEADERS.index(self.FUNCTIONING) elif property.name() == 'relationship': col = self.HEADERS.index(self.RELATIONSHIP) if col is not None: self.dataChanged.emit(self.index(row, col), self.index(row, col)) def addEvent(self, after=None): """ This won't get saved until it's assigned a parent """ event = objects.Event(self.source) row = 0 if after: row = self.indexForEvent(after).row() + 1 self.beginInsertRows(QModelIndex(), row, row) self.events.insert(row, event) self.endInsertRows()
-
Are you also using a custom view ?