Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

custom sorting in mvc (QSortFilterProxyModel) only works once

I'm trying to set up a custom sort behaviour for my model/view structure in PySide. All items are dictionaries and I need to filter by their keys (I'm not after a table view of those dictionaries but rather need to present them as one entity each).

I inherited from QSortFilterProxyModel and re-implemented the lessThan method. It works fine the first time the sort widget is changed (which triggers the proxy's sort() method), but after that the lessThan method is no longer called. I have no idea why and an hoping to get some help here. I'm more than happy to consider any suggestions how to tackle this in a different way if that is where the solution lies.

This is my proxy:

class ProxyModel ( QSortFilterProxyModel ):
    def __init__( self, parent=None ):
        super( ProxyModel, self).__init__( parent )
        self.setFilterCaseSensitivity( Qt.CaseInsensitive )
        self.setSortCaseSensitivity( Qt.CaseInsensitive )
        self.setDynamicSortFilter( True )

    def sortBy( self, attr ):
        print 'sorting by', attr
        self.__sortBy = attr
        self.sort( 0, Qt.AscendingOrder ) # THIS DOES NOT GET CALLED WHEN THE COMBO BOX CHANGES A SECOND TIME

    def lessThan( self, left, right ):
        '''Custom sorting behaviour'''
        leftTool = ( self.sourceModel().itemFromIndex( left ) )
        rightTool = ( self.sourceModel().itemFromIndex( right ) )
        leftData = leftTool.data()[ self.__sortBy ]
        rightData = rightTool.data()[ self.__sortBy ]
        return leftData < rightData

And here is the complete test code: import sys from PySide.QtGui import * from PySide.QtCore import *

class MainWidget( QWidget ) :
    def __init__( self, parent=None ):
        super( MainWidget, self ).__init__()

        self.listView = MyListView()
        model = MyModel()

        # MODELS AND VIEWS
        self.proxyModel = ProxyModel()
        self.proxyModel.setSourceModel( model )
        self.listView.setModel(self.proxyModel)

        # LAYOUTS
        verticalLayout = QVBoxLayout()
        filterLayout = QHBoxLayout()

        # SORTING WIDGET
        sortLayout = QHBoxLayout()
        sortLabel = QLabel( 'sort:' )
        self.sortWidget = QComboBox()
        self.sortWidget.addItems( ['title', 'author', 'downloads'] )
        self.sortWidget.currentIndexChanged.connect( self.sortTools )

        sortLayout.addWidget( sortLabel )
        sortLayout.addWidget( self.sortWidget )

        verticalLayout.addLayout( filterLayout )
        verticalLayout.addLayout( sortLayout )
        verticalLayout.insertWidget(0, self.listView)

        self.setLayout( verticalLayout )

    def sortTools( self ):
        text = self.sortWidget.currentText()
        self.proxyModel.sortBy( text )

class ProxyModel ( QSortFilterProxyModel ):
    def __init__( self, parent=None ):
        super( ProxyModel, self).__init__( parent )
        self.setFilterCaseSensitivity( Qt.CaseInsensitive )
        self.setSortCaseSensitivity( Qt.CaseInsensitive )
        self.setDynamicSortFilter( True )

    def sortBy( self, attr ):
        print 'sorting by', attr
        self.__sortBy = attr
        self.sort( 0, Qt.AscendingOrder ) # THIS DOES NOT GET CALLED WHEN THE COMBO BOX CHANGES A SECOND TIME

    def lessThan( self, left, right ):
        '''Custom sorting behaviour'''
        leftTool = ( self.sourceModel().itemFromIndex( left ) )
        rightTool = ( self.sourceModel().itemFromIndex( right ) )
        leftData = leftTool.data()[ self.__sortBy ]
        rightData = rightTool.data()[ self.__sortBy ]
        return leftData < rightData


class MyListView( QListView ):
    def __init__( self, parent=None ):
        super( MyListView, self).__init__( parent )
        self.setEditTriggers( QListView.NoEditTriggers )
        self.setViewMode( QListView.IconMode )
        self.setMovement( QListView.Static )
        self.setResizeMode( QListView.Adjust )
        self.setDragEnabled( True )


class MyModel( QStandardItemModel ):
    def __init__( self, parent=None ):
        super( MyModel, self).__init__( parent )
        self.init_data()

    def init_data(self):
        row = 0
        toolData = [ {'title':'ToolA', 'author':'John Doe', 'downloads':123, 'category':'color'},
                     {'title':'ToolB', 'author':'me', 'downloads':13, 'category':'color'},
                     {'title':'ToolC', 'author':'you', 'downloads':321, 'category':'transform'},
                     {'title':'ToolD', 'author':'unknown', 'downloads':2, 'category':'transform'}]

        for tool in toolData:
            item = QStandardItem( '%(title)s by %(author)s (%(category)s) - %(downloads)s downloads' % tool )
            item.setData( tool )
            self.setItem( row, 0, item )
            row += 1




if __name__ == '__main__':
    app = QApplication( sys.argv )

    mainWidget = MainWidget()
    mainWidget.resize( 400, 400 )
    mainWidget.show()

    sys.exit( app.exec_() )
like image 878
Frank Rueter Avatar asked Oct 20 '25 02:10

Frank Rueter


1 Answers

Basically Qt "thinks" your Proxy is already sorted. To be precise it checks in C++ whether:

(d->dynamic_sortfilter && 
 d->proxy_sort_column == column && 
 d->sort_order == order)

So, to solve your problem, you can either set dynamic_sortfilter to False (it will have side-effects) or, maybe better, invalidate your sorting:

def sortBy( self, attr ):
    print 'sorting by', attr
    self.__sortBy = attr
    self.invalidate() #invalidate helps 
    self.sort( 0, Qt.AscendingOrder )
like image 157
KCiebiera Avatar answered Oct 22 '25 06:10

KCiebiera



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!