Hello Human
Hello Human

Reputation: 584

List of items with QML and PyQt

I want to create a simple list of GUI elements arranged in a list using PyQt + QML approach. Elements represent projects with two main properties: name and state. Initially the list is filling with some in-code objects, for example from the QT settings storage. The thing is, I want to wrap every entry retrieved from the storage in my class (dedicated module). In pseudo-code:

from my_module import Project

initial_list = [{
    name: 'Spaceship',
    state: 'In progress'
}, {
    name: 'Aircraft',
    state: 'Done'
}, {
    name: 'Truck',
    state: 'Broken'
}]

projects = [Project(p) for p in initial_list]

User can click on the item and perform some tasks with it (thats why I want the element to be a Project instance).

I understand that I should have a model representing the data but I struggle with the scattered examples I found. Should I have a model of the single element as well as the whole list model? How to encapsulate a Project instance in a QObject subclass? My last tries were QAbstractListModel to use as a model and ListView to use as a visual representation. Can you provide me a minimal complete example?

Upvotes: 2

Views: 1906

Answers (1)

Hello Human
Hello Human

Reputation: 584

Guess I've figured it out by myself. For all who somehow will want to repeat the task: you indeed need to subclass QAbstractItemModel (QAbstractListModel) to provide a model for your QML (you don't need to subclass a single item, though I found approaches when it is a valid case).

Python:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys

from PyQt5.QtCore import QCoreApplication, QUrl, QAbstractItemModel, pyqtProperty, QAbstractListModel, QModelIndex, \
    QObject, QVariant, Qt
from PyQt5.QtGui import QGuiApplication

from PyQt5.QtQuick import QQuickView

import mylib


class ProjectListItem(QObject):
    def __init__(self, project: mylib.Project, parent=None):
        super().__init__(parent)
        self.project = project

    @pyqtProperty('QString')
    def name(self):
        return self.project.path.name

    @pyqtProperty('QString')
    def state(self):
        return str(self.project.state)


class ProjectsList(QAbstractListModel):
    def __init__(self, projects: list, parent=None):
        super().__init__(parent)
        self.projects = projects

    def rowCount(self, parent=None, *args, **kwargs):
        return len(self.projects)

    def data(self, index: QModelIndex, role=None):
        # print(index, role)
        if role == Qt.DisplayRole:
            return self.projects[index.row()]

    def addProject(self, project):
        self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
        self.projects.append(project)
        self.endInsertRows()


if __name__ == '__main__':
    app = QGuiApplication(sys.argv)

    projects = ProjectsList([])
    projects.addProject(ProjectListItem(mylib.Project('Abc')))
    projects.addProject(ProjectListItem(mylib.Project('Def')))
    projects.addProject(ProjectListItem(mylib.Project('Ghi')))

    view = QQuickView()
    view.setResizeMode(QQuickView.SizeRootObjectToView)
    view.rootContext().setContextProperty('projectsModel', projects)
    view.setSource(QUrl('main.qml'))

    sys.exit(app.exec_())

QML:

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("PyQt5 love QML")
    color: "whitesmoke"

    GridLayout {
        columns: 2
        rows: 1

        ListView {
            width: 200; height: 250
            model: projectsModel
            delegate: Item {
                id: projectListItem
                width: ListView.view.width
                height: 40
                Column {
                    Text { text: '<b>Name:</b> ' + display.name }
                    Text { text: '<b>State:</b> ' + display.state }
                }
                MouseArea {
                    anchors.fill: parent
                    onClicked: { projectListItem.ListView.view.currentIndex = index; }
                }
            }
            highlight: Rectangle { color: "lightsteelblue"; radius: 5 }
            focus: true
        }
    }
}

So then you'll get something like this:

enter image description here

Upvotes: 3

Related Questions