我是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
您不需要使用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")
}