【问题标题】:How to build QML ListElements from Python3 array of strings of arbitrary length如何从 Python3 任意长度的字符串数组构建 QML ListElements
【发布时间】:2020-04-29 02:24:44
【问题描述】:

我是 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】:

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

型号:型号

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

模型提供用于创建项目的数据集 风景。可以使用 ListModel 在 QML 中直接创建模型, 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")
}

【讨论】:

  • 你的回答太棒了。谢谢。
  • 我正在使用 QStringListModel。谢谢你把它包括在内。它帮助我学习。如何使 ListView 刷新?我有一个按钮可以删除列表中的选定文件,但我不知道如何使 ListView 刷新剩余的文件。
  • @MountainX-for-Monica 仅在 python 端实现:@pyqtSlot() def clear(self): self._model.setStringList([]) 和您使用的 QML 端:backend.clear()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-27
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多