Search code examples
qtpyqtpyqt4qlistwidgetqtreewidget

Can a QListWidget have groupings?


I currently have a QListWidget that displays many items that are user selectable (and dragable). In my application, when an item is checked it will be reordered above the unchecked items. The user can also drag/drop to adjust the order of the checked items.

The problem the users have is that there are a TON of these check boxes and they are not grouped logically on the screen. Thus, I'd like to introduce grouping of some kind. Here is an example of how it currently works.

from PyQt4 import QtGui, QtCore
import sys

rows = [
    {'text': 'Row1', 'value': 1, 'group': 1},
    {'text': 'Row2', 'value': 2, 'group': 1},
    {'text': 'Row3', 'value': 3, 'group': 1},
    {'text': 'Row4', 'value': 4, 'group': 2},
    {'text': 'Row5', 'value': 5, 'group': 2},
    {'text': 'Row6', 'value': 6, 'group': 3},
    {'text': 'Row7', 'value': 7, 'group': 3},
    {'text': 'Row8', 'value': 8, 'group': 3},
    {'text': 'Row9', 'value': 9, 'group': 2},
    {'text': 'Row10', 'value': 10, 'group': 'testing'}
]

class MyList(QtGui.QListWidget):
    def __init__(self):
        QtGui.QListWidget.__init__(self)
        for row in rows:
            item = QtGui.QListWidgetItem(row['text'])
            # These are utilizing the ItemDataRole; 33 and 34 are among the first user defined values
            # http://pyqt.sourceforge.net/Docs/PyQt4/qt.html#ItemDataRole-enum
            item.setData(33, row['value'])
            item.setData(34, row['group'])
            item.setFlags(QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
            item.setCheckState(QtCore.Qt.Unchecked)
            self.addItem(item)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    my_list = MyList()
    my_list.show()
    sys.exit(app.exec_())

This produces an application like this:

Example

What I want to do is group the items that have similar groups under a similar heading. When an item is checked, it'd appear above the groups. Initially, this looks like a QTreeWidget/View, except that the checked items need to appear outside of the existing tree.

Example (text output):

[CHECKED ITEMS APPEAR HERE]
Group 1
  Row1
  Row2
  Row3
Group 2
  Row4
  Row5
  Row9
Group 3
  Row6
  Row7
  Row8
Group testing
  Row10

Is there a way to group items in a QListWidget, preferable so that the 'header' can be selected and all child elements can be autoselected?


Solution

  • to list items in groups here a simalar question: How to list items as groups in QListWidget

    if headeritems and normal items are different in one property they can be handled differently in slot. I'm not quite certain, if it's the way you want. I tried to place checked items on the top and select all items of a group by click on the headeritem.

    group selection by click oh headeritem

    By selecting another signal and modyfiing the slot there are many possibilities, to change the behaviour. Here my code (i tried it in PyPt5 by replacing QtGui by QtWidgets

    from PyQt4 import QtGui, QtCore
    import sys
    
    rows = [
        {'text': 'Row1', 'value': 1, 'group': 1},
        {'text': 'Row2', 'value': 2, 'group': 1},
        {'text': 'Row3', 'value': 3, 'group': 1},
        {'text': 'Row4', 'value': 4, 'group': 2},
        {'text': 'Row5', 'value': 5, 'group': 2},
        {'text': 'Row6', 'value': 6, 'group': 3},
        {'text': 'Row7', 'value': 7, 'group': 3},
        {'text': 'Row8', 'value': 8, 'group': 3},
        {'text': 'Row9', 'value': 9, 'group': 2},
        {'text': 'Row10', 'value': 10, 'group': 'testing'}
    ]
    
    grouptitles = [1, 2, 3,'testing']                       # list of grouptitles
    
    def gruppe(d):                                  # function for sorting the itemlist
        return str(d['group'])
    
    rows.sort(key=gruppe,reverse=False)                     # sort rows by groups
    
    class MyList(QtGui.QListWidget):
        def __init__(self):
            QtGui.QListWidget.__init__(self)
            self.setMinimumHeight(270)
            for t in grouptitles:                           
                item = QtGui.QListWidgetItem('Group {}'.format(t))
                item.setData(33, 'header')
                item.setData(34, t)
                item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
                self.addItem(item)
                for row in rows:
                    if row['group'] == t:
                        item = QtGui.QListWidgetItem(row['text'])
                        # These are utilizing the ItemDataRole; 33 and 34 are among the first user defined values
                        # http://pyqt.sourceforge.net/Docs/PyQt4/qt.html#ItemDataRole-enum
                        item.setData(33, row['value'])
                        item.setData(34, row['group'])
                        item.setFlags(QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
                        item.setCheckState(QtCore.Qt.Unchecked)
                        self.addItem(item)
                    else:
                        pass
    
            self.setSelectionMode(QtGui.QAbstractItemView.MultiSelection)   # 
            self.itemClicked.connect(self.selManager)               # select an appropriate signal
    
        def selManager(self, item):
            if item.data(33) == 'header':
                groupcode = item.data(34)
                for i in range(0,self.count()):
                    if self.item(i).data(34) == groupcode and self.item(i).data(33) != 'header':
                        b = True if self.item(i).isSelected() == False else False
                        self.item(i).setSelected(b)
            else:           
                if item.checkState() == QtCore.Qt.Unchecked:
                    item.setCheckState(QtCore.Qt.Checked)
                    self.moveItem(self.currentRow(),0)    
                else:
                    item.setCheckState(QtCore.Qt.Unchecked)
                    text = 'Group {}'.format(item.data(34))
                    new = self.indexFromItem(self.findItems(text, QtCore.Qt.MatchExactly)[0]).row() # find the row of the headeritem
                    self.moveItem(self.currentRow(), new)               # moving back to group
    
        def moveItem(self, old, new):                       # from row(old) to row(new)
            ni = self.takeItem(old)
            self.insertItem(new,ni)
    
    if __name__ == '__main__':
        app = QtGui.QApplication(sys.argv)
        my_list = MyList()
        my_list.show()
        sys.exit(app.exec_())