Question

I am trying to create a QAbstractListView for use with a QComboBox which maintains a sorted list of the items it contains. I've included some sample code below that illustrates my problem. When I update the items in the list, the currentIndex of the combo box does not update to reflect the changes to the model. I've tried using the rowsAboutToBeInserted and rowsInserted signals, but I can't see any effect (maybe I'm doing it wrong?).

My actual use case is a little more complex, but the example should be enough. The items being sorted aren't just strings, and take a little more effort to sort and have a ItemDataRole different to their DisplayRole.

The itemsAdded and itemsRemoved are my own functions, which will be connected to signals from another list which I'm trying to proxy.

To trigger the problem, press the 'Insert "c"' button. The string is inserted into the list correctly, but the selection moves from 'e' to 'd' (i.e. the selection index does not change).

example code

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from PyQt4 import QtCore, QtGui

class Model(QtCore.QAbstractListModel):
    def __init__(self, *args, **kwargs):
        QtCore.QAbstractListModel.__init__(self, *args, **kwargs)
        self.items = []

    def rowCount(self, parent=QtCore.QModelIndex()):
        return len(self.items)

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if index.isValid() is True:
            if role == QtCore.Qt.DisplayRole:
                return QtCore.QVariant(self.items[index.row()])
            elif role == QtCore.Qt.ItemDataRole:
                return QtCore.QVariant(self.items[index.row()])
        return QtCore.QVariant()

    def itemsAdded(self, items):
        # insert items into their sorted position
        items = sorted(items)
        row = 0
        while row < len(self.items) and len(items) > 0:
            if items[0] < self.items[row]:
                self.items[row:row] = [items.pop(0)]
                row += 1
            row += 1
        # add remaining items to end of list
        if len(items) > 0:
            self.items.extend(items)

    def itemsRemoved(self, items):
        # remove items from list
        for item in items:
            for row in range(0, len(self.items)):
                if self.items[row] == item:
                    self.items.pop(row)
                    break

def main():
    app = QtGui.QApplication([])
    w = QtGui.QWidget()
    w.resize(300,300)
    layout = QtGui.QVBoxLayout()

    model = Model()
    model.itemsAdded(['a','b','d','e'])

    combobox = QtGui.QComboBox()
    combobox.setModel(model)
    combobox.setCurrentIndex(3)
    layout.addWidget(combobox)

    def insertC(self):
        model.itemsAdded('c')

    button = QtGui.QPushButton('Insert "c"')
    button.clicked.connect(insertC)
    layout.addWidget(button)

    w.setLayout(layout)
    w.show()
    app.exec_()

if __name__ == '__main__':
    main()
Was it helpful?

Solution 2

I guess you need to modify the selection index yourself, i.e. something like

if row < currentIndex():
    setCurrentIndex( currentIndex() + 1 );

You should however read the following passage:

Models that provide interfaces to resizable list-like data structures can provide implementations of insertRows() and removeRows(). When implementing these functions, it is important to call the appropriate functions so that all connected views are aware of any changes:

• An insertRows() implementation must call beginInsertRows() before inserting new rows into the data structure, and it must call endInsertRows() immediately afterwards.

• A removeRows() implementation must call beginRemoveRows() before the rows are removed from the data structure, and it must call endRemoveRows() immediately afterwards.

OTHER TIPS

A complete working example below, based on Tim's answer.

The call to setCurrentIndex isn't required. The view keeps track of this automatically when insertRows/removeRows are called correctly.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from PyQt4 import QtCore, QtGui

class Model(QtCore.QAbstractListModel):
    def __init__(self, *args, **kwargs):
        QtCore.QAbstractListModel.__init__(self, *args, **kwargs)
        self.items = []

    def rowCount(self, parent=QtCore.QModelIndex()):
        return len(self.items)

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if index.isValid() is True:
            if role == QtCore.Qt.DisplayRole:
                return QtCore.QVariant(self.items[index.row()])
            elif role == QtCore.Qt.ItemDataRole:
                return QtCore.QVariant(self.items[index.row()])
        return QtCore.QVariant()

    def itemsAdded(self, items):
        # insert items into their sorted position
        items = sorted(items)
        row = 0
        while row < len(self.items) and len(items) > 0:
            if items[0] < self.items[row]:
                self.beginInsertRows(QtCore.QModelIndex(), row, row)
                self.items.insert(row, items.pop(0))
                self.endInsertRows()
                row += 1
            row += 1
        # add remaining items to end of the list
        if len(items) > 0:
            self.beginInsertRows(QtCore.QModelIndex(), len(self.items), len(self.items) + len(items) - 1)
            self.items.extend(items)
            self.endInsertRows()

    def itemsRemoved(self, items):
        # remove items from the list
        for item in items:
            for row in range(0, len(self.items)):
                if self.items[row] == item:
                    self.beginRemoveRows(QtCore.QModelIndex(), row, row)
                    self.items.pop(row)
                    self.endRemoveRows()
                    break

def main():
    app = QtGui.QApplication([])
    w = QtGui.QWidget()
    w.resize(300,200)
    layout = QtGui.QVBoxLayout()

    model = Model()
    model.itemsAdded(['a','b','d','e'])

    combobox = QtGui.QComboBox()
    combobox.setModel(model)
    combobox.setCurrentIndex(3)
    layout.addWidget(combobox)

    def insertC(self):
        model.itemsAdded('c')

    def removeC(self):
        model.itemsRemoved('c')

    buttonInsert = QtGui.QPushButton('Insert "c"')
    buttonInsert.clicked.connect(insertC)
    layout.addWidget(buttonInsert)

    buttonRemove = QtGui.QPushButton('Remove "c"')
    buttonRemove.clicked.connect(removeC)
    layout.addWidget(buttonRemove)

    w.setLayout(layout)
    w.show()
    app.exec_()

if __name__ == '__main__':
    main()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top