как автоматически вставлять/редактировать QAbstractListModel в обновлениях python и qml?


я пытаюсь вставить/отредактировать список python, который является подклассом QAbstractListModel в pyqt5. этот список Python читается в свойстве model элемента ListView в qml. у меня нет проблем с отображением данных в qml. проблема возникает, когда я пытаюсь добавить новые данные в список python.

вот что я сделал до сих пор:

main.py:

import sys, model2
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQuick import QQuickView

class MainWindow(QQuickView):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.model = model2.PersonModel()
        self.rootContext().setContextProperty('PersonModel', self.model)
        self.rootContext().setContextProperty('MainWindow', self)
        self.setSource(QUrl('test2.qml'))

myApp = QApplication(sys.argv)
ui = MainWindow()
ui.show()
sys.exit(myApp.exec_())

model2.py

from PyQt5.QtCore import QAbstractListModel, Qt, pyqtSignal, pyqtSlot

class PersonModel(QAbstractListModel):

    Name = Qt.UserRole + 1
    Age = Qt.UserRole + 2

    personChanged = pyqtSignal()

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

    def data(self, QModelIndex, role):
        row = QModelIndex.row()
        if role == self.Name:
            return self.persons[row]["name"]
        if role == self.Age:
            return self.persons[row]["age"]

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

    def roleNames(self):
        return {
            Qt.UserRole + 1: b'name',
            Qt.UserRole + 2: b'age'
        }

    @pyqtSlot()
    def addData(self):
        self.beginResetModel()
        self.persons = self.persons.append({'name': 'peter', 'age': 22})
        self.endResetModel()
        print(self.persons)

    @pyqtSlot()
    def editData(self):
        print(self.model.persons)

test2.qml:

import QtQuick 2.6
import QtQuick.Controls 2.2

Rectangle {
    anchors.fill: parent
    color: "lightgrey"

    ListView {
        id: listExample
        anchors.fill: parent
        model: PersonModel
        delegate: Text {
            text: name + " " + age
        }
    }

    Button {
        width: 50
        height: 25
        anchors.bottom: parent.bottom
        text: "add"
        onClicked: {
            console.log("qml adding")
            PersonModel.addData()
        }
    }

    .
    .
    .
}

ошибка возникает, когда я нажимаю кнопку добавления, которая вызывает метод addData в model2.py. ошибка лежит в rowCount и в сообщении об ошибке написано TypeError: object of type 'NoneType' has no len(). нужно ли мне испускать изменения или передавать какое-либо значение индекса и роли, чтобы qml знал, что такое новое/старое, и только соответствующим образом отражал изменения?

любая форма руководства очень ценится!


person eugeneoei    schedule 18.10.2017    source источник


Ответы (2)


Ошибка, которую вы получаете, вызвана следующей строкой кода:

self.persons = self.persons.append({'name': 'peter', 'age': 22})

Это вызвано тем, что функция добавления ничего не возвращает, поэтому предполагалось присвоить None для self.persons

Чтобы вставить новые данные, вы должны вызвать beginInsertRows() и endInsertRows(), чтобы уведомить представление об изменении.

метод данных должен быть идентичен указанному в документации, т.е. иметь следующий формат:

def data(self, index, role=Qt.DisplayRole):

То же самое с методом rowCount:

def rowCount(self, parent=QModelIndex()):

Я реализовал методы addPerson, editPerson и deletePerson, которые добавляют, редактируют и удаляют данные из списка соответственно. Также я добавил необходимые элементы в .qml, чтобы иметь возможность его протестировать.

model2.py

from PyQt5.QtCore import QAbstractListModel, Qt, pyqtSignal, pyqtSlot, QModelIndex    

class PersonModel(QAbstractListModel):

    NameRole = Qt.UserRole + 1
    AgeRole = Qt.UserRole + 2

    personChanged = pyqtSignal()

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

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

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

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

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

    @pyqtSlot(int, str, int)
    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())

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

test2.qml

import QtQuick 2.6
import QtQuick.Controls 2.2

Rectangle {
    anchors.fill: parent
    color: "lightgrey"

    ListView {
        id: listExample
        anchors.fill: parent
        model: PersonModel
        delegate:
            Item {
            width: 200
            height: 60
            Row {
                Text {
                    width: 60
                    text:  name + " " + age
                    horizontalAlignment: Text.AlignHCenter
                    anchors.verticalCenter: parent.verticalCenter
                }
                Button{
                    width: 20
                    text: "+"
                    onClicked: PersonModel.editPerson(index, name, age+1)
                }
                Button{
                    width: 20
                    text: "-"
                    onClicked: PersonModel.editPerson(index, name, age-1)
                }
                Button{
                    width: 20
                    text: "X"
                    onClicked: PersonModel.deletePerson(index)
                }
            }
        }
    }

    Button {
        width: 50
        height: 25
        anchors.bottom: parent.bottom
        anchors.right: parent.right
        text: "add"
        onClicked: {
            console.log("qml adding")
            PersonModel.addPerson("luis", 22)
        }
    }
}

Редактировать:

.py

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

.qml

 PersonModel.insertPerson(2, "luis", 1111)
person eyllanesc    schedule 18.10.2017
comment
Эй, Эйланеск! спасибо, что снова ответили на мой вопрос о pyqt5 и qml! я пытался поиграть со вторым и третьим параметрами метода beginInsertRows, но я не могу вставить недавно добавленные данные между jon и jane. как это должно быть сделано? да, кстати, вы случайно не знаете какие-либо ресурсы по pyqt5 и qml? - person eugeneoei; 19.10.2017
comment
@eugeneoei Хорошо, через несколько минут я создам метод вставки. По второму пункту официальной документации нет, на самом деле официальная документация PyQt указывает на документацию Qt, поэтому, если вы хоть немного знаете C++, вы можете это понять. Пожалуйста, не забудьте отметить мой ответ как правильный. - person eyllanesc; 19.10.2017
comment
Круто! теперь я знаю, где я ошибся. пытался добавить словарь в определенную позицию в списке. это точно не сработает. все еще очень новичок в python. по крайней мере, я знаю теперь. спасибо эйланеск! - person eugeneoei; 19.10.2017

Возможно, это ошибка:

self.dataChanged.emit(ix, ix, self.roleNames())

Ведь объявляется сигнал:

personChanged = pyqtSignal()

То есть должно быть:

self.personChanged.emit(ix, ix, self.roleNames())
person test tests    schedule 14.09.2020