Question

I am setting the widgets inside a QListWidget to my own ProductItemWidget class to be able to have more customization over the items in the list. This works fine, but I am unable to get the list to sort automatically. I call

my_list.setSortingEnabled(True)

and then I try to overwrite the "<" comparator of the ProductListItem, which I created specifically to be able to overwrite this function. In the comparator, I try to access the widget corresponding to the two items that are compared and call their getText() functions. The issue here is, the __ lt __ function by default casts the second argument to QListWidgetItem, so in my code I get ProductListItem for self, but I get QListWidgetItem for otherItem. The otherItem does not let me access the ProductItemWidget that it corresponds to because the only way to access it is by passing in the ProductListItem to the QListWidget's itemWidget() call. Here is my code:

class ProductListItem(QListWidgetItem):
    def __init__(self, parent=None):
        super(ProductListItem, self).__init__(parent)

    def __lt__(self, otherItem):

        this_item_widget = self.listWidget().itemWidget(self)
        other_item_widget = otherItem.listWidget().itemWidget(otherItem)

        return this_item_widget.getText() < other_item_widget.getText()

class ProductItemWidget(QWidget):
    def __init__(self, product_name, parent=None):
        super(ProductItemWidget, self).__init__(parent)
        self.label = QLabel(product_name)
        ... setup code ...

    def getText(self):
        return self.label.text()

Is there any way to prevent the call to __ lt __ from casting the otherItem to QListWidgetItem?

I've been stuck on this problem for a while so any tips are appreciated. I am willing to change my entire approach.

Was it helpful?

Solution

QListWidget is one of the "convenience" classes (like QTreeWidget and QTableWidget). Using them is fine as long as your requirements are quite simple. But as soon as you want something a little more sophisticated, the inflexibility soon begins to show.

You can solve your problems fairly easily by switching to the more generic QListView class with a a QStandardItemModel. This requires a little more work to set up, but it will immediately bring a lot more flexibility.

Here's a demo of that approach based on your sample code:

from PyQt4 import QtGui

class ProductListItem(QtGui.QStandardItem):
    def __lt__(self, other):
        listview = self.model().parent()
        this_widget = listview.indexWidget(self.index())
        other_widget = listview.indexWidget(other.index())
        return this_widget.getText() < other_widget.getText()

class ProductItemWidget(QtGui.QWidget):
    def __init__(self, product_name, parent=None):
        super(ProductItemWidget, self).__init__(parent)
        self.label = QtGui.QLabel(product_name, self)
        layout = QtGui.QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.label)

    def getText(self):
        return self.label.text()

class Window(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        self.list = QtGui.QListView(self)
        layout = QtGui.QHBoxLayout(self)
        layout.addWidget(self.list)
        # model must have the listview as parent
        model = QtGui.QStandardItemModel(self.list)
        self.list.setModel(model)
        for key in 'MHFCLNIBJDAEGK':
            item = ProductListItem()
            model.appendRow(item)
            widget = ProductItemWidget('Item %s' % key, self.list)
            self.list.setIndexWidget(item.index(), widget)
        model.sort(0)

if __name__ == '__main__':

    import sys
    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 150, 300)
    window.show()
    sys.exit(app.exec_())
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top