Selectable-list-of-Python-objects-in-QML: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
 
(11 intermediate revisions by 5 users not shown)
Line 1: Line 1:
[[Category:LanguageBindings::PySide]]<br />[[Category:snippets]]<br />[[Category:Developing_with_Qt::Qt Quick]]<br />[[Category:Developing_with_Qt::Qt Quick::QML]]<br />[[Category:Developing_with_Qt::Qt Quick::Tutorial]]<br />[toc align_right=&quot;yes&amp;quot; depth=&quot;2&amp;quot;]


= Selectable list of Python objects in QML =


This [[PySide]] code example shows you how to display a list of arbitrary Python objects in [[QML]] and to get the &quot;real&amp;quot; Python object in a callback when the user clicks on a list item. This is done by wrapping the Python objects inside a QObject. Of course, if you are developing a new application, you can create your model entity objects directly as QObject subclasses, but for adding a QML UI on top of existing Python code, this should be very useful.
[[Category:PySide]]
[[Category:snippets]]
[[Category:Developing_with_Qt::Qt Quick]]
[[Category:Developing_with_Qt::Qt Quick::QML]]
[[Category:Developing_with_Qt::Qt Quick::Tutorial]]
 
{| class="wikitable"
|-
| style="background: #ff6961;text-align: center;"| Attention
|-
| This is a page dedicated to PySide (Qt4). For recent development on PySide2 (Qt5) and PySide6 (Qt6) refer to [[Qt for Python]]
|}
 
This [[PySide]] code example shows you how to display a list of arbitrary Python objects in [[QML]] and to get the "real" Python object in a callback when the user clicks on a list item. This is done by wrapping the Python objects inside a QObject. Of course, if you are developing a new application, you can create your model entity objects directly as QObject subclasses, but for adding a QML UI on top of existing Python code, this should be very useful.


This example consists of two files:
This example consists of two files:
Line 18: Line 29:
This is pretty straightforward. If you don't have OpenGL support on your target platform (or you don't want to use OpenGL-accelerated QML), simply remove the line '''from PySide import QtOpenGL'''.
This is pretty straightforward. If you don't have OpenGL support on your target platform (or you don't want to use OpenGL-accelerated QML), simply remove the line '''from PySide import QtOpenGL'''.


<code><br />import sys
<code>
import sys


from PySide import QtCore<br />from PySide import QtGui<br />from PySide import QtDeclarative<br />from PySide import QtOpenGL<br /></code>
from PySide import QtCore
from PySide import QtGui
from PySide import QtDeclarative
from PySide import QtOpenGL
</code>


=== Define a simple QObject wrapper ===
=== Define a simple QObject wrapper ===


Create a new QObject subclass that gets your custom Python object (called &quot;thing&amp;quot; in this example) as constructor parameter. You can then define different properties (QtCore.Property) for all the parts of the thing that you want to show in the UI. You will use these property names to access attributes of your object in QML later.
Create a new QObject subclass that gets your custom Python object (called "thing" in this example) as constructor parameter. You can then define different properties (QtCore.Property) for all the parts of the thing that you want to show in the UI. You will use these property names to access attributes of your object in QML later.
 
<code>
class ThingWrapper(QtCore.QObject):
    def __init__(self, thing):
        QtCore.QObject.__init__(self)
        self._thing = thing


<code><br />class ThingWrapper(QtCore.QObject):<br /> def ''init''(self, thing):<br /> QtCore.QObject.''init''(self)<br /> self._thing = thing
    def _name(self):
        return str(self._thing)
   
    @property
    def thing(self):
        return self._thing
   
    changed = QtCore.Signal()


def _name(self):<br /> return str(self._thing)
    name = QtCore.Property(unicode, name, notify=changed)
</code>


changed = QtCore.Signal()
=== Define your list model for your objects ===
You can subclass QAbstractListModel and implement the required methods to provide a proper data model to QML's ListView. Theoretically, you could have multiple columns, but for simplicity, we just use one here, and then access the different attributes of our objects (one object per "row"). The list model here gets a list of (wrapped) "things" as constructor parameter. You could also generate objects on the fly if you want, just make sure that you return the proper value in the '''rowCount''' function.


name = QtCore.Property(unicode, ''name, notify=changed)<br /></code>
<code>
<br />h3. Define your list model for your objects
class ThingListModel(QtCore.QAbstractListModel):
<br />You can subclass QAbstractListModel and implement the required methods to provide a proper data model to QML's ListView. Theoretically, you could have multiple columns, but for simplicity, we just use one here, and then access the different attributes of our objects (one object per &quot;row&amp;quot;). The list model here gets a list of (wrapped) &quot;things&amp;quot; as constructor parameter. You could also generate objects on the fly if you want, just make sure that you return the proper value in the '''rowCount''' function.
    COLUMNS = ('thing',)
<br /><code><br />class ThingListModel(QtCore.QAbstractListModel):<br /> COLUMNS = ('thing',)
<br /> definit<span class="things self,">:<br /> QtCore.QAbstractListModel.</span>init''_(self)<br /> self._things = things<br /> self.setRoleNames(dict(enumerate(ThingListModel.COLUMNS)))


def rowCount(self, parent=QtCore.QModelIndex()):<br /> return len(self._things)
    def __init__(self, things):
        QtCore.QAbstractListModel.__init__(self)
        self._things = things
        self.setRoleNames(dict(enumerate(ThingListModel.COLUMNS)))


def data(self, index, role):<br /> if index.isValid() and role == ThingListModel.COLUMNS.index('thing'):<br /> return self._things[index.row()]<br /> return None<br /></code>
    def rowCount(self, parent=QtCore.QModelIndex()):
        return len(self._things)
 
    def data(self, index, role):
        if index.isValid() and role == ThingListModel.COLUMNS.index('thing'):
            return self._things[index.row()]
        return None
</code>


=== Create a controller for receiving events ===
=== Create a controller for receiving events ===


For receiving events from QML, there are several possiblities. We simply create another QObject subclass and give it a Slot with one parameter (the wrapper object in the selected row). We pass the wrapper object from QML, and arrive at this point in the &quot;Python world&amp;quot; and can do whatever we want with the object the user has selected.
For receiving events from QML, there are several possiblities. We simply create another QObject subclass and give it a Slot with one parameter (the wrapper object in the selected row). We pass the wrapper object from QML, and arrive at this point in the "Python world" and can do whatever we want with the object the user has selected.


<code><br />class Controller(QtCore.QObject):<br /> &amp;#64;QtCore.Slot(QtCore.QObject)<br /> def thingSelected(self, wrapper):<br /> print 'User clicked on:', wrapper._thing.name<br /> if wrapper.''thing.number &gt; 10:<br /> print 'The number is greater than ten!'<br /></code>
<code>
<br />h3. Set up the QDeclarativeView
class Controller(QtCore.QObject):
<br />Here follows some generic boilerplate code for setting up the basic QApplication, QMainWindow and QDeclarativeView object tree to display QML. If you don't want to use OpenGL acceleration, remove the lines '''glw = QtOpenGL.QGLWidget()''' and '''view.setViewport(glw)'''.
    @QtCore.Slot(QtCore.QObject)
<br /><code><br />app = QtGui.QApplication(sys.argv)
    def thingSelected(self, wrapper):
<br />m = QtGui.QMainWindow()
        print('User clicked on:', wrapper.thing.name)
<br />view = QtDeclarative.QDeclarativeView()<br />glw = QtOpenGL.QGLWidget()<br />view.setViewport(glw)<br />view.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView)<br /></code>
        if wrapper.thing.number > 10:
<br />h3. Your custom Python object
            print('The number is greater than ten!')
<br />In an existing project, you would simply import your data model module and be done with it. Here, we create a dummy Person object (note that it is a pure Python object and does not know anything about QObject or PySide!) and some example data:
</code>
<br /><code><br />class Person(object):<br /> definit<span class="number name, self,">:<br /> self.name = name<br /> self.number = number
 
<br /> def</span>str<span class="self">:<br /> return 'Person &quot;%s&amp;quot; (d)' (self.name, self.number)
=== Set up the QDeclarativeView ===
<br />people = [<br /> Person('Locke', 4),<br /> Person('Reyes', 8),<br /> Person('Ford', 15),<br /> Person('Jarrah', 16),<br /> Person('Shephard', 23),<br /> Person('Kwon', 42),<br />]<br /></code>
Here follows some generic boilerplate code for setting up the basic QApplication, QMainWindow and QDeclarativeView object tree to display QML. If you don't want to use OpenGL acceleration, remove the lines '''glw = QtOpenGL.QGLWidget()''' and '''view.setViewport(glw)'''.
<br />h3. Wrap your custom objects in QObjects
 
<br />As we have defined our ThingWrapper class above, this is pretty straightforward using Python's list comprehensions:
<code>
<br /><code><br />things = [ThingWrapper(thing) for thing in people]<br /></code>
app = QtGui.QApplication(sys.argv)
<br />h3. Connect the controller, the model and load the QML file
 
<br />Again, this is easy, as we have already written most of the code and just need to glue all the parts together. Using '''setContextProperty''' we can expose QObject instances to the QML engine and access them using the name given as first parameter:
m = QtGui.QMainWindow()
<br /><code><br />controller = Controller()<br />thingList = ThingListModel(things)
 
<br />rc = view.rootContext()
view = QtDeclarative.QDeclarativeView()
<br />rc.setContextProperty('controller', controller)<br />rc.setContextProperty('pythonListModel', thingList)
glw = QtOpenGL.QGLWidget()
<br />view.setSource('PythonList.qml')<br /></code>
view.setViewport(glw)
<br />h3. Show the window and start the application
view.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView)
<br />Add the view to the window, show the window and finally start the application:
</code>
<br /><code><br />m.setCentralWidget(view)
 
<br />m.show()
=== Your custom Python object ===
<br />app.exec</span>''()<br /></code>
In an existing project, you would simply import your data model module and be done with it. Here, we create a dummy Person object (note that it is a pure Python object and does not know anything about QObject or PySide!) and some example data:
 
<code>
class Person(object):
    def __init__(self, number, name):
        self.name = name
        self.number = number
 
    def __str__(self):
        return 'Person "{}" {}'.format(self.name, self.number)
 
people = [
    Person('Locke', 4),
    Person('Reyes', 8),
    Person('Ford', 15),
    Person('Jarrah', 16),
    Person('Shephard', 23),
    Person('Kwon', 42),
]
</code>
 
=== Wrap your custom objects in QObjects ===
As we have defined our ThingWrapper class above, this is pretty straightforward using Python's list comprehensions:
 
<code>
things = [ThingWrapper(thing) for thing in people]
</code>
 
=== Connect the controller, the model and load the QML file ===
Again, this is easy, as we have already written most of the code and just need to glue all the parts together. Using '''setContextProperty''' we can expose QObject instances to the QML engine and access them using the name given as first parameter:
 
<code>
controller = Controller()
thingList = ThingListModel(things)
 
rc = view.rootContext()
 
rc.setContextProperty('controller', controller)
rc.setContextProperty('pythonListModel', thingList)
 
view.setSource('PythonList.qml')
</code>
 
=== Show the window and start the application ===
Add the view to the window, show the window and finally start the application:
 
<code>
m.setCentralWidget(view)
 
m.show()
 
app.exec_()
</code>


== PythonList.qml ==
== PythonList.qml ==
Line 77: Line 168:


* '''model''' references the list model, the '''pythonListModel''' corresponds to the model set using '''setContextProperty''' in Python
* '''model''' references the list model, the '''pythonListModel''' corresponds to the model set using '''setContextProperty''' in Python
* A nifty way to have alternating background colors: '''color: ((index % 2 == 0) ? &quot;#222&amp;quot; : &quot;#111&amp;quot;)'''
* A nifty way to have alternating background colors: '''color: ((index % 2 == 0) ? "#222" : "#111")'''
* The '''text''' attribute of the '''Text''' item is taken from '''model.thing.name''' where '''model''' is our model, '''thing''' is the column of our model, and '''name''' is the property of our wrapper
* The '''text''' attribute of the '''Text''' item is taken from '''model.thing.name''' where '''model''' is our model, '''thing''' is the column of our model, and '''name''' is the property of our wrapper
* When the item is clicked (MouseArea), the '''controller''' (from '''setContextProperty''') gets its '''thingSelected''' slot called with '''model.thing''' as the first and only parameter
* When the item is clicked (MouseArea), the '''controller''' (from '''setContextProperty''') gets its '''thingSelected''' slot called with '''model.thing''' as the first and only parameter


<code><br />import Qt 4.7
<code>
import Qt 4.7
ListView {
    id: pythonList
    width: 400
    height: 200


ListView {<br /> id: pythonList<br /> width: 400<br /> height: 200
    model: pythonListModel


model: pythonListModel
    delegate: Component {
        Rectangle {
            width: pythonList.width
            height: 40
            color: ((index % 2 == 0)?"#222":"#111")


delegate: Component {<br /> Rectangle {<br /> width: pythonList.width<br /> height: 40<br /> color: ((index % 2 == 0)?&quot;#222&amp;quot;:&quot;#111&amp;quot;)<br /> Text {<br /> id: title<br /> elide: Text.ElideRight<br /> text: model.thing.name<br /> color: &quot;white&amp;quot;<br /> font.bold: true<br /> anchors.leftMargin: 10<br /> anchors.fill: parent<br /> verticalAlignment: Text.AlignVCenter<br /> }<br /> MouseArea {<br /> anchors.fill: parent<br /> onClicked: { controller.thingSelected(model.thing) }<br /> }<br /> }<br /> }<br />}<br /></code>
            Text {
                id: title
                elide: Text.ElideRight
                text: model.thing.name
                color: "white"
                font.bold: true
                anchors.leftMargin: 10
                anchors.fill: parent
              verticalAlignment: Text.AlignVCenter
            }
 
            MouseArea {
                anchors.fill: parent
                onClicked: { controller.thingSelected(model.thing) }
            }
        }
    }
}
</code>


== Starting the example ==
== Starting the example ==


This is how it looks when you run the example using '''python PythonList.py''':
This is how it looks when you run the example using '''python PythonList.py''':
[[Image:Pylist.jpg]]

Latest revision as of 10:33, 24 February 2022


Attention
This is a page dedicated to PySide (Qt4). For recent development on PySide2 (Qt5) and PySide6 (Qt6) refer to Qt for Python

This PySide code example shows you how to display a list of arbitrary Python objects in QML and to get the "real" Python object in a callback when the user clicks on a list item. This is done by wrapping the Python objects inside a QObject. Of course, if you are developing a new application, you can create your model entity objects directly as QObject subclasses, but for adding a QML UI on top of existing Python code, this should be very useful.

This example consists of two files:

  • PythonList.py - Our Python code
  • PythonList.qml - The corresponding QML UI

PythonList.py

This is the main module. After finishing writing it, you can start it using python PythonList.py

Import the required modules

This is pretty straightforward. If you don't have OpenGL support on your target platform (or you don't want to use OpenGL-accelerated QML), simply remove the line from PySide import QtOpenGL.

import sys

from PySide import QtCore
from PySide import QtGui
from PySide import QtDeclarative
from PySide import QtOpenGL

Define a simple QObject wrapper

Create a new QObject subclass that gets your custom Python object (called "thing" in this example) as constructor parameter. You can then define different properties (QtCore.Property) for all the parts of the thing that you want to show in the UI. You will use these property names to access attributes of your object in QML later.

class ThingWrapper(QtCore.QObject):
    def __init__(self, thing):
        QtCore.QObject.__init__(self)
        self._thing = thing

    def _name(self):
        return str(self._thing)
    
    @property
    def thing(self):
        return self._thing
    
    changed = QtCore.Signal()

    name = QtCore.Property(unicode, name, notify=changed)

Define your list model for your objects

You can subclass QAbstractListModel and implement the required methods to provide a proper data model to QML's ListView. Theoretically, you could have multiple columns, but for simplicity, we just use one here, and then access the different attributes of our objects (one object per "row"). The list model here gets a list of (wrapped) "things" as constructor parameter. You could also generate objects on the fly if you want, just make sure that you return the proper value in the rowCount function.

class ThingListModel(QtCore.QAbstractListModel):
    COLUMNS = ('thing',)

    def __init__(self, things):
        QtCore.QAbstractListModel.__init__(self)
        self._things = things
        self.setRoleNames(dict(enumerate(ThingListModel.COLUMNS)))

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

    def data(self, index, role):
        if index.isValid() and role == ThingListModel.COLUMNS.index('thing'):
            return self._things[index.row()]
        return None

Create a controller for receiving events

For receiving events from QML, there are several possiblities. We simply create another QObject subclass and give it a Slot with one parameter (the wrapper object in the selected row). We pass the wrapper object from QML, and arrive at this point in the "Python world" and can do whatever we want with the object the user has selected.

class Controller(QtCore.QObject):
    @QtCore.Slot(QtCore.QObject)
    def thingSelected(self, wrapper):
        print('User clicked on:', wrapper.thing.name)
        if wrapper.thing.number > 10:
            print('The number is greater than ten!')

Set up the QDeclarativeView

Here follows some generic boilerplate code for setting up the basic QApplication, QMainWindow and QDeclarativeView object tree to display QML. If you don't want to use OpenGL acceleration, remove the lines glw = QtOpenGL.QGLWidget() and view.setViewport(glw).

app = QtGui.QApplication(sys.argv)

m = QtGui.QMainWindow()

view = QtDeclarative.QDeclarativeView()
glw = QtOpenGL.QGLWidget()
view.setViewport(glw)
view.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView)

Your custom Python object

In an existing project, you would simply import your data model module and be done with it. Here, we create a dummy Person object (note that it is a pure Python object and does not know anything about QObject or PySide!) and some example data:

class Person(object):
    def __init__(self, number, name):
        self.name = name
        self.number = number

    def __str__(self):
        return 'Person "{}" {}'.format(self.name, self.number)

people = [
    Person('Locke', 4),
    Person('Reyes', 8),
    Person('Ford', 15),
    Person('Jarrah', 16),
    Person('Shephard', 23),
    Person('Kwon', 42),
]

Wrap your custom objects in QObjects

As we have defined our ThingWrapper class above, this is pretty straightforward using Python's list comprehensions:

things = [ThingWrapper(thing) for thing in people]

Connect the controller, the model and load the QML file

Again, this is easy, as we have already written most of the code and just need to glue all the parts together. Using setContextProperty we can expose QObject instances to the QML engine and access them using the name given as first parameter:

controller = Controller()
thingList = ThingListModel(things)

rc = view.rootContext()

rc.setContextProperty('controller', controller)
rc.setContextProperty('pythonListModel', thingList)

view.setSource('PythonList.qml')

Show the window and start the application

Add the view to the window, show the window and finally start the application:

m.setCentralWidget(view)

m.show()

app.exec_()

PythonList.qml

QML is pretty compact, and we only need to define the look of one row, and QML will take care of rendering every row separately. The important parts here are:

  • model references the list model, the pythonListModel corresponds to the model set using setContextProperty in Python
  • A nifty way to have alternating background colors: color: ((index % 2 == 0) ? "#222" : "#111")
  • The text attribute of the Text item is taken from model.thing.name where model is our model, thing is the column of our model, and name is the property of our wrapper
  • When the item is clicked (MouseArea), the controller (from setContextProperty) gets its thingSelected slot called with model.thing as the first and only parameter
import Qt 4.7
ListView {
    id: pythonList
    width: 400
    height: 200

    model: pythonListModel

    delegate: Component {
        Rectangle {
            width: pythonList.width
            height: 40
            color: ((index % 2 == 0)?"#222":"#111")

            Text {
                id: title
                elide: Text.ElideRight
                text: model.thing.name
                color: "white"
                font.bold: true
                anchors.leftMargin: 10
                anchors.fill: parent
               verticalAlignment: Text.AlignVCenter
            }

            MouseArea {
                anchors.fill: parent
                onClicked: { controller.thingSelected(model.thing) }
            }
        }
    }
}

Starting the example

This is how it looks when you run the example using python PythonList.py:

Pylist.jpg