qlistviewpyside6python-3.10qabstractlistmodelqidentityproxymodel

QListView & QIdentityProxyModel


I have a test code for QListView & QIdentityProxyModel.

When I implement QIdentityProxyModel, the error occured at the setData , func of dataChanged.If I don't implement it, the error didn't occur.

I tried to do the same thing by PyQt6, but the result was the same.

I tried to do by command prompt, no error massage.

On the contrary, I can success implementing QIdentityProxyModel on the case of QTreeView, QTableView with no problem.I have been thinking QListView is the easiest in these viewer.

Where is the problem in my code?

from PySide6 import QtWidgets, QtGui, QtCore
import sys

class ListView(QtWidgets.QListView):

    def __init__(self,  parent=None):
        super(ListView, self).__init__(parent)  
         
    def insertItem(self):
         self.model().insertRows(0, 1)
         self.model().setData(self.model().index(0, 0), ListItem(text="Hallo!"))   
                
class ListItem(object):

    def __init__(self, text="", parent=None):
        super(ListItem, self).__init__()
        self.data = []
        self.text = "Hi!" if not text else text      
 
class ListModel(QtCore.QAbstractListModel):

    def __init__(self, parent=None):
        super(ListModel, self).__init__(parent)           
        self.items = []

    def columnCount(self, parent=QtCore.QModelIndex()):
        return 1

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

    def headerData(self, index):
        return "LIST"

    def index(self, row, column, _parent=QtCore.QModelIndex()):    
        if row >= len(self.items) - 1:
            return QtCore.QModelIndex()
        if self.items:            
            childIndex = self.createIndex(row, column, self.items[row])      
            return childIndex
        return QtCore.QModelIndex()

    def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
        if not index.isValid():
            return None
        row = index.row()
        column = index.column()
        if role == QtCore.Qt.DisplayRole:
            if 0 <= row < self.rowCount() and 0 <= column < self.columnCount():        
                return self.items[row].text
        
    def setData(self, index, value, role=QtCore.Qt.ItemDataRole.EditRole):
        if role == QtCore.Qt.ItemDataRole.EditRole:
            row = index.row()
            if 0 <= row < self.rowCount():               
                self.items[row] = value
                self.dataChanged["QModelIndex", "QModelIndex"].emit(index, index)               
                return True                          
        return False

    def flags(self, index):
        return QtCore.Qt.ItemFlag.ItemIsEnabled|QtCore.Qt.ItemFlag.ItemIsSelectable

    def index(self, row, column, parent=QtCore.QModelIndex()):
        if row > len(self.items) - 1:
            return QtCore.QModelIndex()
        elif self.items:            
            childIndex = self.createIndex(row, column, self.items[row])      
            return childIndex
        return QtCore.QModelIndex()

    def insertRows(self, position, rows=1, parent=QtCore.QModelIndex()):
        self.beginResetModel()
        self.beginInsertRows(parent, position, position+rows-1)
        for i in range(rows):
            self.items.insert(position+i, ListItem())
        self.endInsertRows()
        self.dataChanged["QModelIndex", "QModelIndex"].emit(parent, parent)
        self.layoutChanged.emit()
        self.endResetModel()
        return True

class ListIdentityModel(QtCore.QIdentityProxyModel):

    def __init__(self, parent=None):
        super(ListIdentityModel, self).__init__(parent)

    def mapFromSource(self, sourceIndex):
        if not sourceIndex.isValid():
            return QtCore.QModelIndex()        
        return self.createIndex(sourceIndex.row(), sourceIndex.column(), sourceIndex.internalPointer())  

    def mapSelectionFromSource(self, selection):
        sourceIndexes = selection.indexes()
        proxySelection = QtCore.QItemSelection(sourceIndexes[0], sourceIndexes[-1])
        return proxySelection        

    def mapSelectionToSource(self, selection):
        proxyIndexes = selection.indexes()
        sourceSelection = QtCore.QItemSelection(proxyIndexes[0], proxyIndexes[-1])
        return sourceSelection    

    def mapToSource(self, proxyIndex):
        if not proxyIndex.isValid():
            return QtCore.QModelIndex()
        return self.sourceModel().createIndex(proxyIndex.row(), proxyIndex.column(), proxyIndex.internalPointer())

    def setSourceModel(self, sourceModel):
        self.beginResetModel()
        super(ListIdentityModel, self).setSourceModel(sourceModel)    
        self.endResetModel()

    
def main():
    app = QtWidgets.QApplication([]) if QtWidgets.QApplication.instance() is None else QtWidgets.QApplication.instance()

    widget = QtWidgets.QWidget()
    pushbutton = QtWidgets.QPushButton("insert item")
    
    main_view = ListView()        
    model = ListModel(main_view)
    main_view.setModel(model)
    #comment or uncomment
    identity_view = ListView()
    identityModel = ListIdentityModel()
    identity_view.setModel(identityModel)
    identityModel.setSourceModel(model)
    #

    hboxlayout = QtWidgets.QHBoxLayout()    
    hboxlayout.addWidget(pushbutton)
    hboxlayout.addWidget(main_view)
    #
    hboxlayout.addWidget(identity_view)
    #
    widget.setLayout(hboxlayout)
    
    pushbutton.clicked.connect(main_view.insertItem)
    
    widget.show()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()

Solution

  • The insertRows() function should not use beginResetModel() (which, as the name suggests, begins a model reset), nor layoutChanged(): not only it should always be called following a layoutAboutToBeChanged(), but that feature should only be used when the model is being sorted.

    Emitting dataChanged() is also pointless, as it's implied that an inserted row will cause the view to call data() on the new indexes.

        def insertRows(self, position, rows=1, parent=QtCore.QModelIndex()):
            self.beginInsertRows(parent, position, position+rows-1)
            for i in range(rows):
                self.items.insert(position+i, ListItem())
            self.endInsertRows()
            return True
    

    Also note that you defined index() twice, but only the second one is used and actually correct, due to the if row > len(self.items) - 1: check opposed to >= of the previous, which is wrong. In any case, if you're using QAbstractListItem, which is a mono dimensional model, there's absolutely no need to override index() and you can use the default implementation.