如何从 Python3 任意长度的字符串数组构建 QML ListElements
How to build QML ListElements from Python3 array of strings of arbitrary length
我是 QML、QtQuick 和 Python 的新手。我想使用 QML 显示文件列表(完整路径)。看来我应该使用 ListView 和 ListElements。我发现的示例和教程都使用硬编码且非常简单的列表数据。我不明白如何从这些示例转到更现实的东西。
如何使用后端的 Python 字符串数组来填充 QML UI 显示的列表?
字符串数组的长度是任意的。我希望列表项是可点击的(可能像 QML url 类型)。他们将打开该 file/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 和内容 - 堆栈
qt - QML ListElement 传递字符串列表 - 堆栈
您不需要使用 ListModel 来填充 ListView,因为 the docs 指出模型可以是列表:
model : model
This property holds the model providing data for the list.
The model provides the set of data that is used to create the items in
the view. Models can be created directly in QML using ListModel,
XmlListModel or ObjectModel, or provided by C++ model classes. If a
C++ model class is used, it must be a subclass of QAbstractItemModel
or a simple list.
(强调我的)
我也推荐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")
}
我是 QML、QtQuick 和 Python 的新手。我想使用 QML 显示文件列表(完整路径)。看来我应该使用 ListView 和 ListElements。我发现的示例和教程都使用硬编码且非常简单的列表数据。我不明白如何从这些示例转到更现实的东西。
如何使用后端的 Python 字符串数组来填充 QML UI 显示的列表?
字符串数组的长度是任意的。我希望列表项是可点击的(可能像 QML url 类型)。他们将打开该 file/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 和内容 - 堆栈
qt - QML ListElement 传递字符串列表 - 堆栈
您不需要使用 ListModel 来填充 ListView,因为 the docs 指出模型可以是列表:
model : model
This property holds the model providing data for the list.
The model provides the set of data that is used to create the items in the view. Models can be created directly in QML using ListModel, XmlListModel or ObjectModel, or provided by C++ model classes. If a C++ model class is used, it must be a subclass of QAbstractItemModel or a simple list.
(强调我的)
我也推荐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")
}