Multi-selection lists in Python with QML

This is an extension of the Selectable list of Python objects in QML and focuses more on modifying the list and getting selected items out of the QML view.

MultiList.py

This is the Python code that takes care of setting up the model and controlling it.

Encoding declaration and module docstring

We can use the docstring as window title later on (using doc):

  1. # -*- coding: utf-8 -*-
  2.  
  3. """Click-your-Zen of PySide"""

Imports

We need sys for the command line arguments and we need this for some free sample data:

  1. import sys
  2. import this
  3.  
  4. from PySide import QtCore
  5. from PySide import QtGui
  6. from PySide import QtDeclarative

QObject wrapper with “checked” property

Just as with the previous example, we now define a wrapper object. The difference here is that it has a “checked” property and a convenience method for toggling the property (don’t forget that you need to emit the “changed” signal, otherwise QML would not know that something has changed!).

  1. class ZenWrapper(QtCore.QObject):
  2.     def __init__(self, zenItem):
  3.         QtCore.QObject.__init__(self)
  4.         self._zenItem = zenItem
  5.         self._checked = False
  6.  
  7.     def _name(self):
  8.         return self._zenItem
  9.  
  10.     def is_checked(self):
  11.         return self._checked
  12.  
  13.     def toggle_checked(self):
  14.         self._checked = not self._checked
  15.         self.changed.emit()
  16.  
  17.     changed = QtCore.Signal()
  18.  
  19.     name = QtCore.Property(unicode, _name, notify=changed)
  20.     checked = QtCore.Property(bool, is_checked, notify=changed)

List model, with helper to get checked items

This is again mostly the same as in the previous example, but we add a new helper method checked that takes care of retrieving all the checked items from the zenItems list:

  1. class ZenListModel(QtCore.QAbstractListModel):
  2.     def __init__(self, zenItems):
  3.         QtCore.QAbstractListModel.__init__(self)
  4.         self._zenItems = zenItems
  5.         self.setRoleNames({0: 'zenItem'})
  6.  
  7.     def rowCount(self, parent=QtCore.QModelIndex()):
  8.         return len(self._zenItems)
  9.  
  10.     def checked(self):
  11.         return [x for x in self._zenItems if x.checked]
  12.  
  13.     def data(self, index, role):
  14.         if index.isValid() and role == 0:
  15.             return self._zenItems[index.row()]

Controller for toggling and retrieving changes

This controller provides a toggled slot that will be called by QML when we click on an item. We toggle the checked state of the item and (for the sake of demonstration) get the list of currently-checked items and print it on the console. We also set the window title to show the amount of currently selected items:

  1. class Controller(QtCore.QObject):
  2.     def toggled(self, model, wrapper):
  3.         global view, __doc__
  4.         wrapper.toggle_checked()
  5.         new_list = model.checked()
  6.         print '='*20, 'New List', '='*20
  7.         print '\n'.join(x.name for x in new_list)
  8.         view.setWindowTitle('%s (%d)' % (__doc__, len(new_list)))

Model and controller setup

Here we generate some example data and instantiate the controller and the list model:

  1. zenItems = [ZenWrapper(zenItem) for zenItem in \
  2.         ''.join([this.d.get(c, c) for c in \
  3.         this.s]).splitlines()[2:]]
  4.  
  5. controller = Controller()
  6. zenItemList = ZenListModel(zenItems)

QApplication / QDeclarativeView + Glue code

This piece of code creates a new QApplication instance, a new QDeclarativeView and also exposes the controller and the list model to the QML environment, so it can be accessed from there:

  1. app = QtGui.QApplication(sys.argv)
  2.  
  3. view = QtDeclarative.QDeclarativeView()
  4. view.setWindowTitle(__doc__)
  5. view.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView)
  6.  
  7. rc = view.rootContext()
  8.  
  9. rc.setContextProperty('controller', controller)
  10. rc.setContextProperty('pythonListModel', zenItemList)
  11. view.setSource(__file__.replace('.py', '.qml'))
  12.  
  13. view.show()
  14. app.exec_()

MultiList.qml

  1. import Qt 4.7
  2.  
  3. ListView {
  4.     id: pythonList
  5.     width: 300
  6.     height: 500
  7.     model: pythonListModel
  8.  
  9.     delegate: Component {
  10.         Rectangle {
  11.             width: pythonList.width
  12.             height: 30
  13.             color: model.zenItem.checked?"#00B8F5":(index%2?"#eee":"#ddd")
  14.             Text {
  15.                 elide: Text.ElideRight
  16.                 text: model.zenItem.name
  17.                 color: (model.zenItem.checked?"white":"black")
  18.                 anchors {
  19.                     verticalCenter: parent.verticalCenter
  20.                     left: parent.left
  21.                     right: (model.zenItem.checked?checkbox.left:parent.right)
  22.                     leftMargin: 5
  23.                 }
  24.             }
  25.             Text {
  26.                 id: checkbox
  27.                 text: "✔"
  28.                 font.pixelSize: parent.height
  29.                 font.bold: true
  30.                 visible: model.zenItem.checked
  31.                 color: "white"
  32.                 anchors {
  33.                     verticalCenter: parent.verticalCenter
  34.                     right: parent.right
  35.                     rightMargin: 5
  36.                 }
  37.             }
  38.             MouseArea {
  39.                 anchors.fill: parent
  40.                 onClicked: { controller.toggled(pythonListModel, model.zenItem) }
  41.             }
  42.         }
  43.     }
  44. }

How the example app looks

Starting the app using python MultiList.py should give something like this:

Screenshot of example multi-selection list

Categories:

  • Tutorial
  • LanguageBindings
  • snippets
  •