如何从Python3任意长度的字符串数组构建QML ListElements

问题描述 投票:0回答:1

我是QML,QtQuick和Python的新手。我想使用QML显示文件列表(完整路径)。看来我应该使用ListView和ListElements。我发现的示例和教程都使用了硬编码且非常简单的列表数据。我不明白如何从这些例子中得出更现实的东西。

如何从后端使用Python字符串数组填充QML UI显示的列表?

字符串数组的长度是任意的。我希望列表项是可单击的(可能是QML url类型)。他们将为该文件/ 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字符串数组打印到控制台,但是目标是使用该字符串数组填充UI中的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和内容-堆栈溢出Dynamically create QML ListElement and content

qt-QML ListElement传递字符串列表-堆栈溢出QML ListElement pass list of strings

python pyqt qml pyqt5 qtquick2
1个回答
0
投票

您不需要使用ListModel来填充ListView,因为the docs指出模型可以是列表:

model:model

此属性保存提供列表数据的模型。

模型提供了用于在其中创建项目的数据集风景。可以使用ListModel,XmlListModel或ObjectModel,或由C ++模型类提供]]。如果一个使用的是C ++模型类,它必须是QAbstractItemModel的子类或简单列表

(我的重点)

我也建议Data Models

在这种情况下,可以通过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")
}
© www.soinside.com 2019 - 2024. All rights reserved.