Reputation: 127
I'm trying to use PySide to update a ListView in QML based on data from a csv file. The csv file is updated by an external program, so I have a loop set up to take the data from that file in a loop.
I am able to get the data into Python and print it, but I think my error is a signal/slot problem, and it's simply not updating in QML.
main.py:
def importSimStatus(statusOutput):
with open(r'status output.csv','r') as readFile:
dummyList2 = statusOutput.outputStatus
i = 0
for j in range(8):
statusOutput.setOutputStatus("", j)
csvReader = csv.reader(readFile)
for row in csvReader:
statusOutput.setOutputStatus(row[0], i)
dummyList2 = statusOutput.outputStatus
i += 1
def checkSimOutput():
for out in range(8):
statusOutput.setOutputStatus("", out)
simResults = []
dummyList = statusOutput.outputStatus
while (dummyList[7] == ""):
try:
importSimStatus(statusOutput)
except:
pass
time.sleep(1)
print(statusOutput.outputStatus)
class CheckSimOutput(QRunnable):
def run(self):
checkSimOutput()
class OutputData(QObject):
statusSig = Signal(list)
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.m_outputStatus = []
def resizeOutputStatus(self, i):
for x in range(i):
self.m_outputStatus.append("")
@Property(list, notify=statusSig)
def outputStatus(self):
return self.m_outputStatus
@outputStatus.setter
def setOutputStatus(self, text, i):
if self.m_outputStatus[i] == text:
return
self.m_outputStatus[i] = text
self.statusSig.emit(self.m_outputStatus)
class Settings(QObject):
simWorkAround = Signal(int)
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.m_simWorkAround = 0
@Property(int, notify=simWorkAround)
def simWorkaround(self):
return self.m_simWorkAround
@simWorkaround.setter
def setSimWorkaround(self, num):
if self.m_simWorkAround == num:
return
self.m_simWorkAround = num
self.simWorkAround.emit(self.m_simWorkAround)
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
settings = Settings()
statusOutput = OutputData()
statusOutput.resizeOutputStatus(8)
def simThread():
simOutRunnable = CheckSimOutput()
QThreadPool.globalInstance().start(simOutRunnable)
model = QStringListModel()
model.setStringList(statusOutput.outputStatus)
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("settings", settings)
engine.rootContext().setContextProperty("myModel", model)
engine.load(QUrl.fromLocalFile('mainfile.qml'))
if not engine.rootObjects():
sys.exit(-1)
settings.simWorkAround.connect(simThread)
statusOutput.statusSig.connect(model.setStringList(statusOutput.outputStatus))
sys.exit(app.exec_())
mainfile.qml:
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.1
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Main Program")
Button {
text: qsTr("Start Draft")
anchors.top: parent.top
anchors.topMargin: 21
anchors.horizontalCenterOffset: 0
anchors.horizontalCenter: parent.horizontalCenter
onClicked: settings.simWorkaround = settings.simWorkaround + 1
}
ListView{
id: listView
x: 0
width: 200
height: 150
anchors.top: parent.top
anchors.topMargin: 55
anchors.horizontalCenter: parent.horizontalCenter
contentWidth: 0
model: myModel
//anchors.fill: parent
delegate: Text { text: model.display }
}
}
As stated, I can get the list to print after it gets imported from the csv file. I can also "preload" the list by adding items like this:
statusOutput.setOutputStatus("foo",0)
statusOutput.setOutputStatus("bar",1)
And with that ahead of "engine.rootContext().setContextProperty("myModel", model)", I can see a list of "foo" and "bar", but nothing happens when clicking my button to run the loops.
How do I get the ListView to refresh as statusOutput is updated?
Upvotes: 2
Views: 1186
Reputation: 244301
You are combining many elements breaking the Single responsibility principle that indicates that each class must have a defined function.
In this case I have created only 2 classes:
FileWorker is a QObject that lives in another thread and that reads the file emitting a signal with the information.
FileManager is a QObject that is exposed to QML and has as Property the model, also has a Slot that allows to reload the data.
main.py:
import os
import csv
from functools import partial
from PySide2 import QtCore, QtGui, QtQml
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class FileWorker(QtCore.QObject):
linesChanged = QtCore.Signal(list)
@QtCore.Slot(str)
def read_csv(self, filename):
lines = []
with open(filename, "r") as f:
csv_reader = csv.reader(f)
for i, row in enumerate(csv_reader):
if i > 7:
break
lines.append(row[0])
self.linesChanged.emit(lines)
class FileManager(QtCore.QObject):
def __init__(self, parent=None):
super(FileManager, self).__init__(parent)
self.m_model = QtCore.QStringListModel(self)
self.m_thread = QtCore.QThread(self)
self.m_thread.start()
self.m_worker = FileWorker()
self.m_worker.moveToThread(self.m_thread)
self.m_worker.linesChanged.connect(self.updateModel)
@QtCore.Property(QtCore.QAbstractItemModel, constant=True)
def model(self):
return self.m_model
@QtCore.Slot()
def load(self):
filename = os.path.join(CURRENT_DIR, "status output.csv")
wrapper = partial(self.m_worker.read_csv, filename)
QtCore.QTimer.singleShot(0, wrapper)
def clean(self):
self.m_thread.quit()
self.m_thread.wait()
@QtCore.Slot(list)
def updateModel(self, lines):
self.m_model.setStringList(lines)
if __name__ == "__main__":
import sys
app = QtGui.QGuiApplication(sys.argv)
engine = QtQml.QQmlApplicationEngine()
filemanager = FileManager()
filemanager.load()
engine.rootContext().setContextProperty("filemanager", filemanager)
filename = os.path.join(CURRENT_DIR, "mainfile.qml")
engine.load(QtCore.QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
res = app.exec_()
filemanager.clean()
sys.exit(res)
mainfile.qml:
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Window 2.2
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Main Program")
Button {
text: qsTr("Start Draft")
anchors.top: parent.top
anchors.topMargin: 21
anchors.horizontalCenterOffset: 0
anchors.horizontalCenter: parent.horizontalCenter
onClicked: filemanager.load()
}
ListView{
id: listView
width: 200
height: 150
anchors.top: parent.top
anchors.topMargin: 55
anchors.horizontalCenter: parent.horizontalCenter
contentWidth: 0
model: filemanager.model
// anchors.fill: parent
delegate: Text { text: model.display }
}
}
Upvotes: 2