I've been trying to make for example 10 different elements for example buttons and I could do that using the repeater but I've been having trouble with setting the text for each new element.
I'm getting the texts I want to set from a list in Python and I sent them to qml through QStringListModel. The text got to qml from the list as I wanted, but somehow the repeater set's the text of all the elements as the last string in the list given from Python.
I will provide the code later for extra explanation but for now I want to see if someone have idea how I can do it...(the thing is that the code is in another device and I'm waiting for it). Basically what I'm trying to do is, let's say for example, I have a list in python a_list = {buttonName, buttonAge, buttonHeight} and I want to make multiple buttons in qml as the size of the list (in this case 3) and change each text of the buttons i made in the repeater as the strings in the list).
this is main.py
import sys
from PySide2.QtCore import QUrl
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from foo import FooController
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
fooController = FooController()
engine.rootContext().setContextProperty("fooController", fooController)
engine.load(QUrl.fromLocalFile('main.qml'))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
This is foo.py
from PySide2.QtCore import QObject, Property, Slot
class x:
def __init__(self, name, age):
self.name = name
self.age = age
class FooController(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.__text = "Foo"
self.__name = s1.name
self.__age = s1.age
#Property(str)
def text(self):
return self.__name
#Slot()
def clickListener(self):
print(self.__name)
This is foo.qml
import QtQuick 2.9
import QtQuick.Controls 2.2
Button {
text: fooController.text
onClicked: fooController.clickListener()
}
and here is the qml window that contains the repeater
import QtQuick 2.0
import "../components"
//import QtQuick.Timeline 1.0
import QtQuick.Controls 2.15
import QtQuick 2.15
import QtQuick.Window 2.15
import QtGraphicalEffects 1.15
import QtQuick.Layouts 1.15
import "../main.py"
window{
width: 1500
height: 920
minimumWidth: 1100
minimumHeight: 650
visible: true
color: "#00000000"
id: mainWindow
title: qsTr("--")
Rectangle{
id: rectangle
anchors.fill: parent
anchors.rightMargin: 0
anchors.bottomMargin: 0
anchors.leftMargin: 0
anchors.topMargin: 0
radius: 10
color: "#4642b6"
Flickable {
id: flickable
contentHeight: gridLayoutBottom.height
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.topMargin: 96
anchors.rightMargin: 8
anchors.leftMargin: 8
anchors.bottomMargin: 8
clip: true
ListModel {
id: imageModel
ListElement { _id: "tile0" }
}
Repeater {
model: imageModel
delegate: CustomMenuType{
ListView{
model: s
delegate: Text {
text: model.display
font.family: "Segoe UI"
color: "#ffffff"
}
}
//text: ListView.delegate.Text.text
font.pointSize: 9
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
}
}
}
}
}
Disclaimer: The code you provide is useless since it does not show any attempt to solve your problem, besides that there are undefined elements that I will not take as a basis for my answer, and it is a shame because you always learn more by correcting errors.
If you want to handle the information that the Repeater uses from Python then you must use a model. The Repeater supports 3 types of models:
A number,
A list or
An object that inherits from QAbstractItemModel.
In this case the first does not provide important information since it only indicates the number of elements so it will not show it since it is a trivial example.
In the case of the list the logic is to create a Q_PROPERTY of type "QVariantList" (in PyQt5 list is enough) and that has an associated signal that is emitted every time the list changes so that the view is notified and updates the painting.
import os.path
import sys
from PySide2.QtCore import Property, QObject, QDateTime, QTimer, QUrl, Signal
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
class Controller(QObject):
modelChanged = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self._model = ["Text1", "Text2", "Text3"]
#Property("QVariantList", notify=modelChanged)
def model(self):
return self._model
def update_model(self, l):
self._model = l[:]
self.modelChanged.emit()
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
controller = Controller()
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("controller", controller)
filename = os.path.join(CURRENT_DIRECTORY, "main.qml")
engine.load(QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
def on_timeout():
dt = QDateTime.currentDateTime()
l = [dt.addSecs(i).toString() for i in range(3)]
controller.update_model(l)
timer = QTimer(timeout=on_timeout, interval=1000)
timer.start()
sys.exit(app.exec_())
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window{
width: 640
height: 480
visible: true
Column {
Repeater {
model: controller.model
Button {
text: model.modelData
}
}
}
}
In the case of QAbstractItemModel the logic is to create a QProperty of type QObject and make it constant since the model itself does not change but the information it manages. And on the QML side, the property must be accessed using the associated role, for example in the case of QStringListModel the role is "display":
import os.path
import sys
from PySide2.QtCore import Property, QDateTime, QObject, QStringListModel, QTimer, QUrl
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
class Controller(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._model = QStringListModel()
self.model.setStringList(["Text1", "Text2", "Text3"])
def get_model(self):
return self._model
model = Property(QObject, fget=get_model, constant=True)
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
controller = Controller()
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("controller", controller)
filename = os.path.join(CURRENT_DIRECTORY, "main.qml")
engine.load(QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
def on_timeout():
dt = QDateTime.currentDateTime()
l = [dt.addSecs(i).toString() for i in range(3)]
controller.model.setStringList(l)
timer = QTimer(timeout=on_timeout, interval=1000)
timer.start()
sys.exit(app.exec_())
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window{
width: 640
height: 480
visible: true
Column {
Repeater {
model: controller.model
Button {
text: model.display
}
}
}
}
You can also create a custom model but you have to declare a role associated with the data, a trivial example is to use the QStandardItemModel class.
import os.path
import sys
from PySide2.QtCore import (
Property,
QDateTime,
QObject,
Qt,
QTimer,
QUrl,
)
from PySide2.QtGui import QGuiApplication, QStandardItem, QStandardItemModel
from PySide2.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
TEXT_ROLE = Qt.UserRole + 1000
DATA_ROLE = TEXT_ROLE + 1
class Controller(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._model = QStandardItemModel()
self.model.setItemRoleNames({TEXT_ROLE: b"text", DATA_ROLE: b"data"})
for i in range(3):
item = QStandardItem()
item.setData("Text{}".format(i), TEXT_ROLE)
item.setData(i, DATA_ROLE)
self.model.appendRow(item)
def get_model(self):
return self._model
model = Property(QObject, fget=get_model, constant=True)
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
controller = Controller()
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("controller", controller)
filename = os.path.join(CURRENT_DIRECTORY, "main.qml")
engine.load(QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
def on_timeout():
dt = QDateTime.currentDateTime()
for i in range(controller.model.rowCount()):
item = controller.model.item(i)
item.setData(dt.addSecs(i).toString(), TEXT_ROLE)
item.setData(dt.addSecs(i), DATA_ROLE)
timer = QTimer(timeout=on_timeout, interval=1000)
timer.start()
sys.exit(app.exec_())
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window{
width: 640
height: 480
visible: true
Column {
Repeater {
model: controller.model
Button {
text: model.text
onClicked: console.log(model.data)
}
}
}
}
You could also create a class that inherits from QAbstractListModel and overrides the data, setData, roleNames, rowCount methods.
Related
I made a QtQuick Window Gui Application for Python 3.8 on Windows. The last thing I cant figure out is how to display Python print() in the Gui Text Area. What i want is, that wherever in my Python code a print statement is and gets executed during runtime, i want to output it into the TextArea in my Gui app
I read the following post, but failed to implemet it, different errors occured and am more confused then before:
the closest and most usefull was this one:
How to capture output of Python's interpreter and show in a Text widget?
and some others:
Python/PyQt/Qt Threading: How do I print stdout/stderr right away?
How to Redirect a Python Console output to a QTextBox
How can I flush the output of the print function?
How do I direct console output to a pyqt5 plainTextEdit widget with Python?
Python Printing StdOut As It Received
working Sample Code to send a string from Python into QML TextArea
main.py
import os
from pathlib import Path
import sys
from vantage import daily
# load GUI libs
from PySide2.QtGui import QGuiApplication
from PySide2.QtCore import QSettings, QObject, Signal, Slot
from PySide2.QtQml import QQmlApplicationEngine
# load app
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml"))
class Backend(QObject):
textwritten = Signal(str, arguments=['writen'])
def __init__(self):
super().__init__()
self.timer = QTimer()
self.timer.setInterval(100)
self.timer.timeout.connect(self.writer)
self.timer.start()
# console output write function
def writer(self):
towrite = 'i am writing'
self.textwritten.emit(str(towrite))
# create an instance of the Python object (Backend class)
back_end = Backend()
# give data back to QML
engine.rootObjects()[0].setProperty('writen', back_end)
# close app
sys.exit(app.exec_())
main.qml
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.15
Window {
width: 640
height: 480
visible: true
color: "#2f2f2f"
title: qsTr("alpha")
/*print out console text*/
property string texted: "Console Text"
property QtObject writen
ScrollView {
id: scrollViewCon
x: 58
y: 306
width: 507
height: 100
ScrollBar.vertical.verticalPadding: 4
ScrollBar.vertical.minimumSize: 0.4
ScrollBar.vertical.contentItem: Rectangle {
implicitWidth: 6
implicitHeight: 100
radius: width / 2
color: control.pressed ? "#81e889" : "#f9930b"
}
TextArea {
font.family: control.font
font.pointSize: 8
color:"#f9930b"
wrapMode: TextEdit.Wrap
KeyNavigation.priority: KeyNavigation.BeforeItem
KeyNavigation.tab: textField
placeholderTextColor : "#f9930b"
opacity: 1
text: texted
placeholderText: texted //qsTr("Console")
readOnly: true
background: Rectangle {
radius: 12
border.width: 2
border.color: "#f9930b"
}
}
}
Connections {
target: writen
function onTextwritten(msg) {
texted = msg;
}
}
}
i think what needs to happen is that everytime sys.stdout is called by print() it emits a signal with itself?
leaving main.qml as is and only changing main.py
main.py
...
class Backend(QObject):
textwritten = Signal(str, arguments=['writen'])
def __init__(self):
super().__init__()
sys.stdout = self.writer(str(sys.stdout))
def writer(self, message):
#towrite = 'i am writing'
self.textwritten.emit(message)
...
The print function writes over sys.stdout so the solution is to assign some QObject that has a write method that emits a signal. For this you can use contextlib.redirect_stdout:
import os
import sys
from contextlib import redirect_stdout
from functools import cached_property
from pathlib import Path
from PySide2.QtCore import (
QCoreApplication,
QDateTime,
QObject,
Qt,
QTimer,
QUrl,
Signal,
)
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = Path(__file__).resolve().parent
class RedirectHelper(QObject):
stream_changed = Signal(str, name="streamChanged", arguments=["stream"])
def write(self, message):
self.stream_changed.emit(message)
class TimerTest(QObject):
#cached_property
def timer(self):
return QTimer(interval=1000, timeout=self.handle_timeout)
def handle_timeout(self):
print(QDateTime.currentDateTime().toString())
def start(self):
self.timer.start()
def main():
ret = 0
redirect_helper = RedirectHelper()
with redirect_stdout(redirect_helper):
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("redirectHelper", redirect_helper)
filename = os.fspath(CURRENT_DIRECTORY / "main.qml")
url = QUrl.fromLocalFile(filename)
def handle_object_created(obj, obj_url):
if obj is None and url == obj_url:
QCoreApplication.exit(-1)
engine.objectCreated.connect(handle_object_created, Qt.QueuedConnection)
engine.load(url)
timer_test = TimerTest()
timer_test.start()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
import QtQuick 2.12
import QtQuick.Controls 2.12
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
Flickable {
id: flickable
flickableDirection: Flickable.VerticalFlick
anchors.fill: parent
TextArea.flickable: TextArea {
id: textArea
anchors.fill: parent
readOnly: true
font.pointSize: 8
color: "#f9930b"
wrapMode: TextEdit.Wrap
placeholderTextColor: "#f9930b"
opacity: 1
placeholderText: qsTr("Console")
background: Rectangle {
radius: 12
border.width: 2
border.color: "#f9930b"
}
}
ScrollBar.vertical: ScrollBar {
}
}
Connections {
function onStreamChanged(stream) {
textArea.insert(textArea.length, stream);
}
target: redirectHelper
}
}
Hello I am using Model class to supply items for lists and comboboxes. The problem is that I use the setContextProperty() function every time for each element. I'm looking for a solution where all elements(list and comboboxes) use the same ContextProperty. Furthermore with this way I guess JSON files can be loaded dynamically instead of loading all of them at the beginning.
main.py
class Model(QAbstractListModel, QObject):
""" it reads JSON file, that is given as argument,
and creates the model"""
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
model1 = Model("file1.json")
model2 = Model("file2.json")
model3 = Model("file3.json")
model4 = Model("file4.json")
model5 = Model("file5.json")
engine.rootContext().setContextProperty("model1", model1)
engine.rootContext().setContextProperty("model2", model2)
engine.rootContext().setContextProperty("model3", model3)
engine.rootContext().setContextProperty("model4", model4)
engine.rootContext().setContextProperty("model5", model5)
engine.rootContext().setContextProperty("applicationDirPath", os.path.dirname(__file__))
engine.load(os.path.join(os.path.dirname(__file__), "main.qml"))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec())
You can create a QObject that exposes all the models as a property list and then use a Repeater to dynamically create the comboboxes.
The following is a demo:
import os
import sys
from pathlib import Path
from PySide6.QtCore import Property, QCoreApplication, QObject, Qt, QUrl, Signal
from PySide6.QtGui import QGuiApplication, QStandardItem, QStandardItemModel
from PySide6.QtQml import QQmlApplicationEngine
CURRENT_DIRECTORY = Path(__file__).resolve().parent
class Model(QStandardItemModel):
def __init__(self, values, parent=None):
super().__init__(parent)
for value in values:
item = QStandardItem(value)
self.appendRow(item)
class Manager(QObject):
models_changed = Signal(name="modelsChanged")
def __init__(self, parent=None):
super().__init__(parent)
self._models = []
#Property("QVariantList", notify=models_changed)
def models(self):
return self._models
def append_model(self, model):
self._models.append(model)
self.models_changed.emit()
def main():
app = QGuiApplication(sys.argv)
manager = Manager(app)
manager.append_model(Model(["item11", "item12", "item13"]))
manager.append_model(Model(["item21", "item22", "item23"]))
manager.append_model(Model(["item31", "item32", "item33"]))
manager.append_model(Model(["item41", "item42", "item43"]))
engine = QQmlApplicationEngine()
context = engine.rootContext()
context.setContextProperty("applicationDirPath", os.fspath(CURRENT_DIRECTORY))
context.setContextProperty("managerModel", manager)
filename = os.fspath(CURRENT_DIRECTORY / "main.qml")
url = QUrl.fromLocalFile(filename)
def handle_object_created(obj, obj_url):
if obj is None and url == obj_url:
QCoreApplication.exit(-1)
engine.objectCreated.connect(handle_object_created, Qt.QueuedConnection)
engine.load(url)
sys.exit(app.exec())
if __name__ == "__main__":
main()
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ApplicationWindow {
width: 640
height: 480
visible: true
ColumnLayout {
anchors.centerIn: parent
Repeater {
id: repeater
model: managerModel.models
ComboBox {
model: modelData
textRole: "display"
Layout.fillWidth: true
}
}
}
}
Side note: A QAbstractListModel is a QObject so the double inherence is useless so you should change it to: class Model(QAbstractListModel):
I try to get the root object after window having completed, but I get a error:
QmlObj = self.engine.rootObjects()[0]
Error: list index out of range
The strange thing is that it works when I try to call foo.init_window() after the MouseArea having clicked.
Here is my python code:
main.py
from PySide2.QtWidgets import QApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import QObject, QUrl, Slot
import sys
import win32gui
flag = False
class Foo(QObject):
def __init__(self):
super().__init__()
self.engine = QQmlApplicationEngine()
#Slot()
def init_window(self):
global flag
if not flag:
QmlObj = self.engine.rootObjects()[0]
desk = win32gui.FindWindow("Progman", "Program Manager")
print(desk)
sndWnd = win32gui.FindWindowEx(desk, 0, "SHELLDLL_DefView", None)
print(sndWnd)
targetWnd = win32gui.FindWindowEx(sndWnd,
0, "SysListView32", "FolderView")
print(targetWnd)
win32gui.SetParent((int)(QmlObj.winId()), targetWnd)
flag = True
if __name__ == "__main__":
app = QApplication(sys.argv)
foo = Foo()
foo.engine.rootContext().setContextProperty("foo", foo)
foo.engine.load(QUrl("main.qml"))
# win = foo.engine.rootObjects()[0]
# win.show()
if not foo.engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
Here is the .qml file:
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 2.0
Window {
width: 200
height: 100
visible: true
//flags: Qt.FramelessWindowHint
//flags: Qt.WindowStaysOnBottomHint
//flags: Qt.WindowMinMaxButtonsHint
Rectangle {
anchors.fill: parent
color: "red"
Component.onCompleted: foo.init_window()
MouseArea {
anchors.fill: parent
onClicked: foo.init_window()
}
Text {
anchors.centerIn: parent
text: "Hello, World!"
}
Button {
text: "Ok"
onClicked: {
console.log("OK Button clicked....")
}
}
}
}
The problem is that in Component.onCompleted the window(the rootObject) has finished building but the engine list has not been updated. The solution is to invoke init_window an instant later using Qt.callLater():
Component.onCompleted: Qt.callLater(foo.init_window)
Trying out a really simple TreeView control for Qt in Python, but for some reason the GUI is just blank.
main.qml
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Controls 1.4 as OldControls
ApplicationWindow {
visible: true
title: qsTr("Simple Tree View")
OldControls.TreeView {
anchors.fill: parent
model: simpleModel
OldControls.TableViewColumn {
role: "display"
title: "Name"
width: 100
}
}
}
main.py
import sys
from os.path import abspath, dirname, join
from PySide2.QtGui import QGuiApplication, QStandardItemModel, QStandardItem
from PySide2.QtQml import QQmlApplicationEngine
class SimpleTreeView(QStandardItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self.setColumnCount(1)
root = self.invisibleRootItem()
group1 = QStandardItem("group1")
group1.setText("group1")
value1 = QStandardItem("value1")
value1.setText("value1")
group1.appendRow(value1)
root.appendRow(group1)
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
qmlFile = join(dirname(__file__), 'main.qml')
engine.rootContext().setContextProperty("simpleModel", SimpleTreeView())
engine.load(abspath(qmlFile))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
Output in Linux
The problem is that since the SimpleTreeView object is not assigned to a variable then it is destroyed, that can be verified using the destroyed signal.
class SimpleTreeView(QStandardItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self.destroyed.connect(lambda o : print("destroyed:", o))
# ...
Output:
destroyed: <PySide2.QtCore.QObject(0x56377cf050f0) at 0x7ffa20deac40>
The solution is to assign a variable to that object so that the life cycle is greater:
qmlFile = join(dirname(__file__), 'main.qml')
model = SimpleTreeView()
engine.rootContext().setContextProperty("simpleModel", model)
# ...
I'm new to QML, QtQuick and Python. I would like to display a list of files (full path) using QML. It seems like I should use a ListView and ListElements. The examples and tutorials I have found all use list data that is hard-coded and very simple. I do not understand how to go from those examples to something more realistic.
How do I use a Python string array from my backend to populate a list displayed by the QML UI?
The length of the string array is arbitrary. I want the list items to be clickable (like a QML url type, possibly). They will open the operating system's default application for that file/url type.
My backend code is similar to this:
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_())
Right now I am just printing the files_list string array from the backend to the console, but the goal is to use that string array to populate the QML list in the UI.
An example of the contents of files_list is:
['/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', '']
(I will need to figure out how to deal with the null string at the end of that array.)
A rough outline of my QML (to the best of my current ability) is like this:
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)
}
}
The most relevant questions I have found are these, but they did not resolve my issue:
qt - Dynamically create QML ListElement and content - Stack Overflow Dynamically create QML ListElement and content
qt - QML ListElement pass list of strings - Stack Overflow QML ListElement pass list of strings
You don't need to use a ListModel to populate a ListView since as the docs points out a model can be a list:
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.
(emphasis mine)
I also recommend Data Models.
In this case, a list can be displayed through a pyqtProperty. On the other hand do not use subprocess.Popen() as it is blocking causing the GUI to freeze, instead use 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")
}
You can also use 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")
}