2
votes

I would like to pass a list of dictionaries from pyqt5 to qml where this list becomes the model of ListView element. The following code is what i have so far.

main.py

class MainWindow(QQuickView):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.model = model.PersonModel()
        self.rootContext().setContextProperty('PersonModel', self.model)
        self.rootContext().setContextProperty('MainWindow', self)
        self.setSource(QUrl('main.qml'))

    @pyqtSlot()
    def personsList(self):
        print(self.model.persons) # this prints the list of objects perfectly fine
        return(self.model.persons)
        .
        .
        .

main.qml

ListView {
    id: listExample
    anchors.fill: parent
    model: MainWindow.personsList()
    delegate: PersonDelegate { }
    highlight: highlightComponent
    highlightMoveDuration: 0
    Component.onCompleted: {
        console.log(">>>", MainWindow.personsList()) // returns undefined, but why??
    }
}

model.py

from PyQt5.QtCore import QAbstractListModel, Qt, QModelIndex 

class PersonModel(QAbstractListModel):

    NAME_ROLE = Qt.UserRole + 1
    AGE_ROLE = Qt.UserRole + 2

    def __init__(self, parent=None):
        super().__init__(parent)
        self.persons = [
            {'name': 'jon', 'age': 20},
            {'name': 'jane', 'age': 25},
            {'name': 'pete', 'age': 25},
            {'name': 'winson', 'age': 25},
            {'name': 'ben', 'age': 25},
            {'name': 'jiahao', 'age': 25}            
        ]

        self.currentIndex = 0

    def data(self, index, role=Qt.DisplayRole):
        row = index.row()
        if role == PersonModel.NAME_ROLE:
            return self.persons[row]["name"]
        if role == PersonModel.AGE_ROLE:
            return self.persons[row]["age"]

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

    def roleNames(self):
        return {
            PersonModel.NAME_ROLE: b'name',
            PersonModel.AGE_ROLE: b'age'
        }

    def updateIndex(self, int):
        self.currentIndex = int
        print("updating index from click", self.currentIndex)

    def addPerson(self, name, age):
        self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
        self.persons.append({'name': name, 'age': age})
        self.endInsertRows()

    def insertPerson(self, row, name, age):
        self.beginInsertRows(QModelIndex(), row, row)
        self.persons.insert(row, {'name': name, 'age': age})
        self.endInsertRows()

    def editPerson(self, row, name, age):
        ix = self.index(row, 0)
        self.persons[row] = {'name': name, 'age': age}
        self.dataChanged.emit(ix, ix, self.roleNames())

    def deletePerson(self, row):
        self.beginRemoveColumns(QModelIndex(), row, row)
        del self.persons[row]
        self.endRemoveRows()

why does the list of persons return undefined in qml?

In case anyone is wondering, i am trying to create a model class that i can instantiate with by passing any list of dictionaries and use this list as the model property in different ListView elements. In this model class i have add, insert, edit, delete that i can use consistently throughout all instances and thus avoid repetitions.

Also, i am trying to separate the model from the view such that the data flow is one way ie qml informs python of any user interaction and all changes in the model will be handled on python which qml will change accordingly whenever model changes.

2
The code that samples of the model is not enough to analyze which is the error, you could place the code of the complete model.eyllanesc
hi eyllanesc! i've edited and placed the full model.py in my question.eugeneoei
The way you interact with python with qml is unsuitable, what do you really want to do?eyllanesc
could you explain why it is unsuitable? im thinking of creating a generic model class that acts like a list generator and take in different lists of dictionaries that will have the same methods. each instance of this generic model will be used as the model property in different ListView in qmleugeneoei
Will the models you create have the same roles? Also why do you want to send a dictionary from python to QML?eyllanesc

2 Answers

2
votes

In Qt there is the Q_INVOKABLE macro that enables and registers the function so that it can be used from QML, in PyQt its equivalent is a pyqtSlot() that we have to tell you what kind of data it returns through the result parameter.

@pyqtSlot(result=list)
def personsList(self):
    print(self.model.persons)
    return self.model.persons

An error that is observed in your QML code is that you are passing as a model to ListView a list of objects instead of the model:

import QtQuick 2.6

ListView {
    id: listExample
    anchors.fill: parent
    model: PersonModel
    delegate: Text{
        text: name
    }
    highlightMoveDuration: 0
    Component.onCompleted: {
        console.log(">>>", MainWindow.personsList()) // returns undefined, but why??
        var l = MainWindow.personsList();
        for(var counter in l){
            console.log(l[counter]['name'] + ": " + l[counter]['age'])
        }
    }
}

Output:

[{'name': 'jon', 'age': 20}, {'name': 'jane', 'age': 25}]
qml: >>> [[object Object],[object Object]]
[{'name': 'jon', 'age': 20}, {'name': 'jane', 'age': 25}]
qml: jon: 20
qml: jane: 25
1
votes

I am in a MeeGo phone, limited to import QtQuick 1.0, and have been trying to send python objects (lists, dictionaries, tuples) via a Slot to a user interface written in qml:

@Slot(str, result=list)

at the qml end I have been trying this:

var l = app.get_taxonomic_derivation(lookup_text.text)
print (l)
print (l.length)

and all I was getting was:

QVariant(PySide::PyObjectWrapper)
undefined

which I did not manage to convert into a list, no idea how.

I ended up using json, with eval at the client side.

@Slot(str, result=str)
def get_taxonomic_derivation(self, epithet):
    ....
    return json.dumps(result)