[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