[PyQt] Example of populating a QML list from Python for use by other QML objects

Elvis Stansvik elvstone at gmail.com
Thu Jan 5 22:30:39 GMT 2017


2017-01-04 19:11 GMT+01:00 Louis Simons <lousimons at gmail.com>:
> Is there an example of populating a QML list of objects from Python so that
> other objects using the members of these lists receive updates when their
> properties are changed?  In my app.qml (simplified psuedocode), I have
> something like:
>
> Item {
>   Store { id: 'store' }
>
>   ChannelsListing {
>     channels: store.channels
>   }
>
>   ChannelsSummary {
>     channels: store.channels
>   }
> }
>
> A channel is a QML object looks like (and has an appropriate Python class
> registered):
>
> Channel {
>   property int id
>   property str name
> }
>
> The ChannelsListing and ChannelSummary are pure QML with no need for Python
> (hopefully).  They simply display the data and update when the data changes.
> They don't change the channels themselves, but instead will send an action
> upwards to whatever back-end the store is getting the data from to modify
> the data.  This should ensure that all views of the data remain consistent.
>
> I can make a Python Store class derived from QQuickItem and register it to
> the Store QML type:
>
> class Store(QQuickItem):
>   def __init__(self):
>     self._channels = []
>
>   @pyqtProperty(QQmlListProperty)
>   def channels(self):
>     return QQmlListProperty(Channel, self, self._channels)
>
> The store also connects to an ZMQ socket, and receives model data for the
> channels in JSON.  However, when I try to use the append functions of a
> QQmlListProperty, I get errors as Store.channels is actually a
> QQmlListPropertyWrapper, which I can "len" and iterate over, but can't
> modify.
>
> Is this approach possible?  Am I trying to put a square peg in a round hole?
> Should I be doing the data modification entirely in QML?  I'm open to any
> suggestions and architecture recommendations.

Maybe not a square peg, but definitely not round I think :)

I have to go to bed, but below is a small example that "works" with
what I think was your approach. After 3 seconds, you'll see
Channel("Baz") being appended from the Python side, and the QML view
update as a result.

But, what I think you'll want to do instead is to turn your list of
channels into a proper QAbstractItemModel. That model would update
itself in response to your 0MQ messages and emit the appropriate
signals. On the QML side you could then hook that model up to pretty
much any standard QML view.

I don't have any other recommendations as I'm somewhat new to QML
myself, except for: Try to keep as much of your model stuff in Python
only, and only do "dumb" presentation/interaction in QML.

Hope this helps a bit!

Elvis


# Example

main.py:

from sys import argv

from PyQt5.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlListProperty, QQmlApplicationEngine, qmlRegisterType


class Channel(QObject):

    nameChanged = pyqtSignal()

    def __init__(self, name='', *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._name = name

    @pyqtProperty('QString', notify=nameChanged)
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        if name != self._name:
            self._name = name
            self.nameChanged.emit()


class Store(QObject):

    channelsChanged = pyqtSignal()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._channels = [
            Channel('Foo'),
            Channel('Bar')
        ]

    @pyqtProperty(QQmlListProperty, notify=channelsChanged)
    def channels(self):
        return QQmlListProperty(Channel, self, self._channels)

    @channels.setter
    def channels(self, channels):
        if channels != self._channels:
            self._channels = channels
            self.channelsChanged.emit()

    def appendChannel(self, channel):
        self._channels.append(channel)
        self.channelsChanged.emit()


def main():
    app = QGuiApplication(argv)

    qmlRegisterType(Channel, 'Example', 1, 0, 'Channel')
    qmlRegisterType(Store, 'Example', 1, 0, 'Store')

    store = Store()

    engine = QQmlApplicationEngine()
    engine.rootContext().setContextProperty('store', store)
    engine.load('main.qml')

    # After 3 seconds, we append a new Channel
    QTimer.singleShot(3000, lambda: store.appendChannel(Channel('Baz')))

    exit(app.exec_())


if __name__ == '__main__':
    main()



main.qml:

import QtQuick 2.4
import QtQuick.Window 2.2

import Example 1.0

Window {
    width: 500
    height: 500
    visible: true

    // Lets pretend this is your ChannelsListing
    ListView {
        anchors.fill: parent
        model: store.channels
        delegate: Text {
            text: name
        }
    }
}


>
> Thanks,
> Louis
>
> _______________________________________________
> PyQt mailing list    PyQt at riverbankcomputing.com
> https://www.riverbankcomputing.com/mailman/listinfo/pyqt


More information about the PyQt mailing list