Как собрать QML ListElements из Python3 массива строк произвольной длины - PullRequest
1 голос
/ 11 января 2020

Я новичок в QML, QtQuick и Python. Я хотел бы отобразить список файлов (полный путь), используя QML. Кажется, я должен использовать ListView и ListElements. Все примеры и учебники, которые я нашел, используют данные списка, которые жестко запрограммированы и очень просты. Я не понимаю, как go из этих примеров к чему-то более реалистичному c.

Как использовать строковый массив Python из моего бэкэнда для заполнения списка, отображаемого QML UI?

Длина массива строк произвольна. Я хочу, чтобы элементы списка были кликабельными (например, тип URL QML). Они откроют приложение по умолчанию для операционной системы для этого типа файла / URL.

Мой внутренний код похож на это:

import sys
from subprocess import Popen, PIPE
import getpass
from PyQt5.QtWidgets import QApplication, QMessageBox
from PyQt5.QtCore import Qt, QCoreApplication, QObject, pyqtSlot
from PyQt5.QtQml import QQmlApplicationEngine

class Backend(QObject):

basepath = '/path/to/files'
list_files_cmd = "find " + basepath + " -type f -readable"

myfiles = Popen(list_files_cmd, shell=True, stdout=PIPE, stderr=PIPE)
output, err = myfiles.communicate()
# the output is a Byte literal like this: b'/path/to/file1.txt\n/path/to/file2.txt\n'. Transform into a regular string:
newstr = output.decode(encoding='UTF-8')
files_list = newstr.split('\n')
for file in files_list:
    print(file)

if __name__ == '__main__':

    backend = Backend()

    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    app = QApplication(sys.argv)
    engine = QQmlApplicationEngine('view.qml')
    engine.rootContext().setContextProperty("backend", backend)
    sys.exit(app.exec_())

Сейчас я просто печатаю строковый массив files_list из бэкэнда в консоль, но цель состоит в том, чтобы использовать этот строковый массив для заполнения списка QML в пользовательском интерфейсе.

Пример содержимого files_list:

['/path/to/files/xdgr/todo.txt', '/path/to/files/xdgr/a2hosting.txt', '/path/to/files/xdgr/paypal.txt', '/path/to/files/xdgr/toggle.txt', '/path/to/files/xdgr/from_kty.txt', '/path/to/files/xdgr/feed59.txt', '/path/to/files/one/sharing.txt', '/path/to/files/two/data.dbx', '']

(Мне нужно выяснить, как обращаться с пустой строкой в ​​конце этого массива.)

Грубый контур моего QML (в меру моих нынешних способностей) выглядит следующим образом:

import QtQml.Models 2.2
import QtQuick.Window 2.2
import QtQuick 2.2
import QtQuick.Controls 1.3
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3

ApplicationWindow {
    visible: true
    TabView {
        anchors.fill: parent
        Tab {
            title: "Files"
            anchors.fill: parent
            ListView {
                id: mListViewId
                anchors.fill: parent
                model: mListModelId
                delegate : delegateId
            }
            ListModel {
                id: mListModelId
                // I would like backend.files_list to provide the model data
            }
        }
    } 
    Component.onCompleted: {
        mListModelId.append(backend.files_list)
    }
}

Наиболее важные вопросы, которые я нашел, это, но они не решили мою проблему:

qt - Динамически создавать QML ListElement и контент - Переполнение стека Динамически создавать QML ListElement и content

qt - QML ListElement передает список строк - Переполнение стека QML ListElement передает список строк

1 Ответ

2 голосов
/ 12 января 2020

Вам не нужно использовать ListModel для заполнения ListView, так как документы указывают, что модель может быть списком:

модель: модель

Это свойство содержит модель, предоставляющую данные для списка.

Модель предоставляет набор данных, который используется для создания элементов в представлении. Модели могут быть созданы непосредственно в QML с использованием ListModel, XmlListModel или ObjectModel или предоставлены классами моделей C ++ . Если используется класс модели C ++, он должен быть подклассом QAbstractItemModel или простым списком .

(выделение мое)

Я также рекомендую Модели данных .

В этом случае список может отображаться через pyqtProperty. С другой стороны, не используйте subprocess.Popen(), поскольку это блокирует вызов GUI замораживания, вместо этого используйте QProcess.

import os
import sys

from PyQt5.QtCore import (
    pyqtProperty,
    pyqtSignal,
    pyqtSlot,
    QCoreApplication,
    QObject,
    QProcess,
    Qt,
    QUrl,
)
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine


class Backend(QObject):
    filesChanged = pyqtSignal()

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

        self._files = []

        self._process = QProcess(self)
        self._process.readyReadStandardOutput.connect(self._on_readyReadStandardOutput)
        self._process.setProgram("find")

    @pyqtProperty(list, notify=filesChanged)
    def files(self):
        return self._files

    @pyqtSlot(str)
    def findFiles(self, basepath):
        self._files = []
        self.filesChanged.emit()
        self._process.setArguments([basepath, "-type", "f", "-readable"])
        self._process.start()

    def _on_readyReadStandardOutput(self):
        new_files = self._process.readAllStandardOutput().data().decode().splitlines()
        self._files.extend(new_files)
        self.filesChanged.emit()


if __name__ == "__main__":

    QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    app = QApplication(sys.argv)

    backend = Backend()

    engine = QQmlApplicationEngine()
    engine.rootContext().setContextProperty("backend", backend)

    current_dir = os.path.dirname(os.path.realpath(__file__))
    filename = os.path.join(current_dir, "view.qml")
    engine.load(QUrl.fromLocalFile(filename))

    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())

view.qml

import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Controls 1.4

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    TabView {
        anchors.fill: parent
        Tab {
            title: "Files"
            ListView {
                id: mListViewId
                clip: true
                anchors.fill: parent
                model: backend.files
                delegate: Text{
                    text: model.modelData
                }
                ScrollBar.vertical: ScrollBar {}
            }
        }
    }
    Component.onCompleted: backend.findFiles("/path/to/files")
}

Вы также можете использовать QStringListModel.

import os
import sys

from PyQt5.QtCore import (
    pyqtProperty,
    pyqtSignal,
    pyqtSlot,
    QCoreApplication,
    QObject,
    QProcess,
    QStringListModel,
    Qt,
    QUrl,
)
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine


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

        self._model = QStringListModel()

        self._process = QProcess(self)
        self._process.readyReadStandardOutput.connect(self._on_readyReadStandardOutput)
        self._process.setProgram("find")

    @pyqtProperty(QObject, constant=True)
    def model(self):
        return self._model

    @pyqtSlot(str)
    def findFiles(self, basepath):
        self._model.setStringList([])
        self._process.setArguments([basepath, "-type", "f", "-readable"])
        self._process.start()

    def _on_readyReadStandardOutput(self):
        new_files = self._process.readAllStandardOutput().data().decode().splitlines()
        self._model.setStringList(self._model.stringList() + new_files)


if __name__ == "__main__":

    QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    app = QApplication(sys.argv)

    backend = Backend()

    engine = QQmlApplicationEngine()
    engine.rootContext().setContextProperty("backend", backend)

    current_dir = os.path.dirname(os.path.realpath(__file__))
    filename = os.path.join(current_dir, "view.qml")
    engine.load(QUrl.fromLocalFile(filename))

    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())

view.qml

import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Controls 1.4

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    TabView {
        anchors.fill: parent
        Tab {
            title: "Files"
            ListView {
                id: mListViewId
                clip: true
                anchors.fill: parent
                model: backend.model
                delegate: Text{
                    text: model.display
                }
                ScrollBar.vertical: ScrollBar {}
            }
        }
    }
    Component.onCompleted: backend.findFiles("/path/to/files")
}
...