1
votes

I'm trying to fill a QML list model from python and on a button press, I'd like to get the item I set previously.

I ran into type conversion problems, where I get QObject instead of a python dictionary.

Create a class which gets pushed to the QML root context. The pySignal is invoked by QML javascript code

import sys
import os

from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlComponent, QJSValue
from PyQt5.QtQml import QQmlEngine, QQmlComponent


class PythonContext(QObject):

    pySignal = pyqtSignal(QJSValue)

    def __init__(self, parent=None):
        QObject.__init__(self, parent)

        self.pySignal.connect(self.onPySignal)

    @pyqtSlot(QJSValue)
    def onPySignal(self, param):
        obj = param.toVariant()
        print('onPySignal', obj)
        print(dir(obj))


if __name__ == '__main__':

    app = QGuiApplication(sys.argv)
    engine = QQmlEngine()

    pyContext = PythonContext()

    ctxt = engine.rootContext()
    ctxt.setContextProperty('py', pyContext)

    component = QQmlComponent(engine)
    component.loadUrl(QUrl('qml_signals.qml'))

    obj = component.create()

    obj.addListItem({'pk': 1, 'name': 'One'})
    obj.addListItem({'pk': 2, 'name': 'Two'})
    obj.addListItem({'pk': 3, 'name': 'Three'})

    engine.quit.connect(app.quit)

    sys.exit(app.exec_())

The QML file itself only contains an application window with a ComboBox and a button. A button press invokes the pySignal emission and should send the item currently selected by the ComboBox to the Python context.

QML:

import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.12
import QtQuick.Dialogs 1.3

ApplicationWindow {

    title: "PyQT5 QML Signals dictionary"

    visible: true
    width: 400
    height: 400

    function addListItem(param) {
        lm.append(param)
        if(lmSelector.count == 1) {
            lmSelector.currentIndex = 0
        }
    }

    ColumnLayout {
        anchors.fill: parent

        ComboBox {
            id: lmSelector
            Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter

            textRole: "name"

            model: ListModel {
                id: lm
            }
        }

        Button {
            text: "button"
            Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
            onClicked: {
                var selectedObject = lm.get(lmSelector.currentIndex)

                console.log("button clicked" + selectedObject.name)

                /* this does not work */
                py.pySignal(selectedObject)

                /* but this does and gives the desired output */
                py.pySignal({ name: selectedObject.name, pk: selectedObject.pk})
            }
        }
    }
}

Output:

onPySignal <PyQt5.QtCore.QObject object at 0x03CA4710>

What I would expect is:

onPySignal {'name': 'One', 'pk': 1.0}
1
@eyllanesc I edited the code. Sorry about that, I hacked this in just before leaving...PirklW

1 Answers

1
votes

Even if you pass a dictionary such as data to the ListModel, it stores it in QObjects so you can implement the binding required. Therefore, the get() method of ListModel returns a object(QObject):

object get(int index)

f you want to recover the dictionary then you must convert that object to JSON, and from JSON to dictionary:

// ...
/* this work */
py.pySignal(JSON.parse(JSON.stringify(selectedObject)))
// ...

Output:

onPySignal {'name': 'One', 'pk': 1}
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
onPySignal {'name': 'One', 'pk': 1.0}
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']