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!


  • Lifetime Qt Champion

    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!


  • Lifetime Qt Champion

    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.


  • Lifetime Qt Champion

    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()
    

  • Lifetime Qt Champion

    Are you also using a custom view ?


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.