Riempire un ListView da un JSON complesso
-
Ciao a tutti,
Sono nuovo sul forum e anche di QT.
Sto realizzando un prototipo a scopo lavorativo di una web app che già abbiamo, e voglio trasformare in un app per migliorare sopratutto le performance.Sto realizzando una pagina per visualizzare una programmazione oraria di un termostato.
La programmazione viene caricata da un JSON tramite una REST API che carico attraverso Javascript. Per il frontend uso QML come linguaggio.
Sono riuscito a ricavarmi il model con tutte le informazioni, ma non riesco a visualizzarle come vorrei.
Mi esce fuori un ListView unico con tutti i periodi di ognisingolo giorno, disposti verticalmente, mentreio vorrei una riga per ogni giorno, e in ogni giorno visualizzare x rettangoli disposti orizzontalmente, tanti quanti sonoi periodi.
Il JSON d'esempio è il seguente:
{"zone":"1","season":"summer","daily_schedule":[{"times_of_operation":[{"start":"0:00","stop":"6:30","temp":"29"},{"start":"6:30","stop":"8:30","temp":"26"},{"start":"8:30","stop":"17:00","temp":"30"},{"start":"17:00","stop":"22:00","temp":"26"},{"start":"22:00","stop":"24:00","temp":"29"}],"weekday":"monday"},{"times_of_operation":[{"start":"0:00","stop":"6:30","temp":"29"},{"start":"6:30","stop":"8:30","temp":"26"},{"start":"8:30","stop":"17:00","temp":"32"},{"start":"17:00","stop":"22:00","temp":"26"},{"start":"22:00","stop":"24:00","temp":"29"}],"weekday":"tuesday"},{"times_of_operation":[{"start":"0:00","stop":"6:30","temp":"29"},{"start":"6:30","stop":"8:30","temp":"26"},{"start":"8:30","stop":"17:00","temp":"32"},{"start":"17:00","stop":"22:00","temp":"26"},{"start":"22:00","stop":"24:00","temp":"29"}],"weekday":"wednesday"},{"times_of_operation":[{"start":"0:00","stop":"6:30","temp":"29"},{"start":"6:30","stop":"8:30","temp":"26"},{"start":"8:30","stop":"17:00","temp":"32"},{"start":"17:00","stop":"22:00","temp":"26"},{"start":"22:00","stop":"24:00","temp":"29"}],"weekday":"thursday"},{"times_of_operation":[{"start":"0:00","stop":"6:30","temp":"29"},{"start":"6:30","stop":"8:30","temp":"26"},{"start":"8:30","stop":"17:00","temp":"32"},{"start":"17:00","stop":"22:00","temp":"26"},{"start":"22:00","stop":"24:00","temp":"29"}],"weekday":"friday"},{"times_of_operation":[{"start":"0:00","stop":"8:30","temp":"29"},{"start":"8:30","stop":"22:00","temp":"26"},{"start":"22:00","stop":"24:00","temp":"29"}],"weekday":"saturday"},{"times_of_operation":[{"start":"0:00","stop":"8:30","temp":"29"},{"start":"8:30","stop":"22:00","temp":"26"},{"start":"22:00","stop":"24:00","temp":"30"}],"weekday":"sunday"}]}
I periodi si ricavano da times_of_operation, e questo è il codice che ho usato per ricavare le informazioni e inserirle nel listview.
ListView { model: ListModel { id: model} delegate: myRectComp; width: parent.width height: parent.height spacing: 2 // 'b' space between elements //orientation: Qt.Horizontal section.property: "weekday" section.criteria: ViewSection.FullString section.delegate: sectionHeading } Component.onCompleted: { var xhr = new XMLHttpRequest; xhr.open("GET", constants.baseURL + "/api/thermostat.php?zone=1&season=summer"); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { var data = JSON.parse(xhr.responseText); model.clear(); var list = data["daily_schedule"]; for (var i in list) { for (var k in list[i].times_of_operation) { var start = list[i].times_of_operation[k].start; var stop = list[i].times_of_operation[k].stop; var temp = list[i].times_of_operation[k].temp; var color = Schedule.color_map(temp, 'summer'); var width = ((Schedule.decode_time(stop) - Schedule.decode_time(start)) / 24) * window.width model.append( { 'weekday': list[i]["weekday"], 'myTemp': temp, 'myWidth': width, 'myColor': color, 'myTime': start } ); } } } } xhr.send(); } Component { id: myRectComp; ColumnLayout { Rectangle { color: myColor; Text { anchors.centerIn: parent color: 'black' text: myTemp font.pixelSize: 12 font.bold: true } width: myWidth; height: 50; } Rectangle { color: '#000'; Text { color: 'white' text: myTime font.pixelSize: 10 font.bold: true } width: myWidth; height: 10; } } } Component { id: sectionHeading Rectangle { width: window.width height: childrenRect.height color: "#000" Text { text: section font.bold: true color: 'white' font.pixelSize: 12 } } } }
Con questo codice ottengo il seguente risultato:
Come già detto le informazioni che ottengo e che visualizzo sono corrette, ma vengono visualizzate male, perchè devo raggruppare i rettangoli sotto ogni giorno, in un unica riga.
Ci sto sbattendo la testa da diversi giorni, spero possiate aiutarmi.
Grazie
-
Hello, Hope that you can translate this.
There are many people recommending to use Treeview instead for this sort of listing.
But as of Qt 5.12 there is a component called TableView.. That component will let you do what you want to.
And there is not much changes needed in your code.
If you need to use any earlier version of Qt then you should use treeview :)This is the basic example loaded from doc.qt.io
import QtQuick 2.14 import Qt.labs.qmlmodels 1.0 TableView { anchors.fill: parent columnSpacing: 1 rowSpacing: 1 clip: true model: TableModel { TableModelColumn { display: "name" } TableModelColumn { display: "color" } rows: [ { "name": "cat", "color": "black" }, { "name": "dog", "color": "brown" }, { "name": "bird", "color": "white" } ] } delegate: Rectangle { implicitWidth: 100 implicitHeight: 50 border.width: 1 Text { text: display anchors.centerIn: parent } } }
Hope this helps!
-
if you really want to, this will work with listview
Rectangle { id:row Rectangle { id:anch color: "#000" Text { width: 50; height: 50; color: 'white' text: myTemp font.pixelSize: 12 font.bold: true } } Rectangle { width: 50; height: 10; anchors.left:anch.right color: '#000'; Text { color: 'white' text: myTime font.pixelSize: 10 font.bold: true } } }
Here I have just placed both rectangles in one master, then anchored the left of one to the right of the other.
-
Hi
I have version 5.12.5 and when I try to put the code that you showed me with TableView, from error in the part of the TableModel, which I read that was introduced by version 5.14. I will try to find an example with TreeView.
Instead regarding the other solution keeping ListView, and changing the last part, it just didn't work.
In that code both the color and the width that I got from the model were missing, adding this information however did not work.
Thanks anyway for now.
-
Hmm.. that I cannot understand..
If you want to, try this one out, just copy to a new qml file and run it.. here i've just created a list based on yours and added anchors to the rectangles no master rect etc..
Works very well for me..
Hope you get treeview or this working :) have a good day
import QtQuick 2.1 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.0 ListView{ model: model delegate: myRectComp; width: parent.width height: parent.height spacing: 2 // 'b' space between elements //orientation: Qt.Horizontal Component { id: myRectComp; ColumnLayout { Rectangle { id:imLeft color: myColor; Text { anchors.centerIn: parent color: 'black' text: myTemp font.pixelSize: 12 font.bold: true } width: myWidth; height: 50; } Rectangle { anchors.left:imLeft.right anchors.top:imLeft.top color: '#000'; Text { color: 'white' text: myTime font.pixelSize: 10 font.bold: true } width: myWidth; height: 10; } } } Component { id: sectionHeading Rectangle { width: window.width height: childrenRect.height color: "#000" Text { text: section font.bold: true color: 'white' font.pixelSize: 12 } } } ListModel { id:model ListElement { myColor: "red"; myWidth: 100; myTime: "120"; myTemp:"1" } ListElement { myColor: "red"; myWidth: 100; myTime: "121"; myTemp:"1" } ListElement { myColor: "red"; myWidth: 100; myTime: "121"; myTemp:"1" } } }
-
Your code does not work, that is, it puts the time on a line with a colored rectangle, but it is not my purpose.
In the image there is monday and below there are 5 squares where each is aligned with the time one below the other.
My goal is to align all the colored squares next to each other, the ones that are under the day.
I'll show you how it should be.
Thank you
-
Aha, I understand.
For me, it was then just to remove the anchors from the last example I sent, then adding orientation: ListView.Horizontal on the listview.
import QtQuick 2.1 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.0 ListView{ model: model delegate: myRectComp; width: 500 //(had to do this because of where i tested it..) height: parent.height orientation: ListView.Horizontal //added this clip:false spacing: 2 // 'b' space between elements //orientation: Qt.Horizontal Component { id: myRectComp; ColumnLayout { Rectangle { color: '#000'; Text { color: 'white' text: myTime font.pixelSize: 10 font.bold: true } width: myWidth; height: 10; } Rectangle { id:imLeft color: myColor; Text { anchors.centerIn: parent color: 'black' text: myTemp font.pixelSize: 12 font.bold: true } width: myWidth; height: 50; } } } Component { id: sectionHeading Rectangle { width: window.width height: childrenRect.height color: "#000" Text { text: section font.bold: true color: 'white' font.pixelSize: 12 } } } ListModel { id:model ListElement { myColor: "red"; myWidth: 100; myTime: "120"; myTemp:"1" } ListElement { myColor: "red"; myWidth: 150; myTime: "121"; myTemp:"1" } ListElement { myColor: "red"; myWidth: 100; myTime: "121"; myTemp:"1" } } }
-
I had already done this test, I have the same orientation effect: Qt.Horizontal.
In practice I have a single row arranged on a horizontal scrolling.
Instead I need the lines of every day arranged vertically, but the lines serve me arranged horizontally.
Let's say that the lines now look good, but they must be arranged vertically.
I hope I have been clear.
Try it with multiple lines, because your example apparently works because you tried only one line. Give an example like mine: use 7 days, and every day must have those rectangles arranged horizontally.
Thank you
-
I write here model that I have for example:
ListModel { id:model ListElement { weekday: "monday"; myColor: "red"; myWidth: 216.6; myTime: "0:00"; myTemp:"29" } ListElement { weekday: "monday"; myColor: "red"; myWidth: 66.66; myTime: "6:30"; myTemp:"26" } ListElement { weekday: "monday"; myColor: "red"; myWidth: 283.33; myTime: "8:30"; myTemp:"30" } ListElement { weekday: "monday"; myColor: "red"; myWidth: 166.66; myTime: "17:00"; myTemp:"26" } ListElement { weekday: "monday"; myColor: "red"; myWidth: 66.66; myTime: "22:00"; myTemp:"29" } ListElement { weekday: "tuesday"; myColor: "red"; myWidth: 216.6; myTime: "0:00"; myTemp:"29" } ListElement { weekday: "tuesday"; myColor: "red"; myWidth: 66.66; myTime: "6:30"; myTemp:"26" } ListElement { weekday: "tuesday"; myColor: "red"; myWidth: 283.33; myTime: "8:30"; myTemp:"30" } ListElement { weekday: "tuesday"; myColor: "red"; myWidth: 166.66; myTime: "17:00"; myTemp:"26" } ListElement { weekday: "tuesday"; myColor: "red"; myWidth: 66.66; myTime: "22:00"; myTemp:"29" } .... ListElement { weekday: "sunday"; myColor: "red"; myWidth: 283.26; myTime: "0:00"; myTemp:"29" } ListElement { weekday: "sunday"; myColor: "red"; myWidth: 450; myTime: "8:30"; myTemp:"26" } ListElement { weekday: "sunday"; myColor: "red"; myWidth: 66.66; myTime: "22:00"; myTemp:"30" } }
The Width of each of the rectangles must make 800 which is the width of the screen. But for testing this is irrelevant.
This is the model more or less, I need to arrange it as I showed in the previous photo.
Thank you
-
This is very hard to do with listview. As a listview inside another listview is very challenging.
But I will create a solution the way I would do it for this sort of project when i get back up again, if no one else has by then :)
-
Could not go to sleep just yet!
Here is a example I modified to fit your purpose a bit better than it was originally, with some creative thinking it should go ok to implement this? Basically you just have to parse the json days (after removing duplicates) into model1 then parse the rest of te data to model2import QtQuick 2.1 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.0 Rectangle { width: 800 height: 500 ListModel { id: model1 ListElement { name: "Monday" } ListElement { name: "Tuesday" } ListElement { name: "Wednsday" } } ListModel { id: model2 ListElement { time: "00" temp: "23" } ListElement { time: "06" temp: "22" } ListElement { time: "08" temp: "24" } } Component { id: delegate2 Item { width: 100 height: col2.childrenRect.height Column { id: col2 anchors.left: parent.left anchors.right: parent.right Text { id: times text: time } Text { anchors.top:times.bottom id: temps text: temp } } } } ListView { width: 800 id: outer model: model1 delegate: listdelegate anchors.fill: parent } Component { id: listdelegate Item { width: 500 height: col.childrenRect.height Column { id: col anchors.left: parent.left anchors.right: parent.right Text { id: t1 text: name } ListView { id: insidelist model: model2 delegate: delegate2 contentHeight: contentItem.childrenRect.height height: childrenRect.height anchors.left: parent.left anchors.right: parent.right clip: false orientation: ListView.Horizontal } } } } }
-
Hello
Of small improvements, however there are errors in the console:
QML Column: Cannot specify top, bottom, verticalCenter, fill or centerIn anchors for items inside Column. Column will not function.
Hoever I have the effect that every day I repeat the same line, which is the same effect that I had had some time ago doing everything differently.
Monday has data different from Tuesday, but Monday's data repeats them the same on all days. I have to distinguish the days in the model when I fill the second listview. How can I do?
And another thing. Every day has daily data from Monday to Sunday, which scrolls horizontally. I think the problem lies with how I build the model in the javascript part.
Thank you
Thank you
-
Morning :)
Yes column does not like that... which is why it's better to use treeview, or leave out the column.
Only thing then is to properly align the elements yourself with anchors etc.Hmm, "And another thing. Every day has daily data from Monday to Sunday, which scrolls horizontally."
So you can scroll down from money to tuesday, and also scroll to the right to see tuesday too? or how do you mean?The double list I sent last night is possible if you dynamicly add listmodels and id's. I have done this with 3D Objects, but not with listmodels, so not quite sure if it will work.
Another way is to use sub-list elements, but then again there is a need to do some changes to parsing, I think that this is the easiest way to achieve what you want without tableview etc
import QtQuick 2.14 Rectangle { width: 200; height: 200 ListModel { id: daysModel ListElement { day: "monday" times: [ ListElement { description: "00:00" }, ListElement { description: "06:30" } ] temps: [ ListElement { description: "17" }, ListElement { description: "21" } ] } ListElement { day: "tuesday" times: [ ListElement { description: "00:00" }, ListElement { description: "06:30" } ] temps: [ ListElement { description: "17" }, ListElement { description: "21" } ] } ListElement { day: "wedsday" times: [ ListElement { description: "00:00" }, ListElement { description: "06:30" } ] temps: [ ListElement { description: "17" }, ListElement { description: "21" } ] } ListElement { day: "thursday" times: [ ListElement { description: "00:00" }, ListElement { description: "06:30" } ] temps: [ ListElement { description: "17" }, ListElement { description: "21" } ] } ListElement { day: "friday" times: [ ListElement { description: "00:00" }, ListElement { description: "06:30" } ] temps: [ ListElement { description: "17" }, ListElement { description: "21" } ] } ListElement { day: "saturday" times: [ ListElement { description: "00:00" }, ListElement { description: "06:30" } ] temps: [ ListElement { description: "17" }, ListElement { description: "21" } ] } ListElement { day: "sunday" times: [ ListElement { description: "00:00" }, ListElement { description: "06:30" } ] temps: [ ListElement { description: "17" }, ListElement { description: "21" } ] } } Component { id: daysDelegate Item { width: 200; height: 50 Text { id: nameField; text: day } Row { id:firstRow anchors.top: nameField.bottom spacing: 5 Text { text: "times:" } Repeater { model: times Text { text: description } } } Row { id:secondRow anchors.top: firstRow.bottom spacing: 5 Text { text: "temps:" } Repeater { model: temps Text { text: description } } } } } ListView { id:listView anchors.fill: parent clip:true model: daysModel delegate: daysDelegate } Rectangle { anchors.top:listView.bottom height:20 Text { id: name text: daysModel.get(0).times.get(1).description; } } }
Here you just need to foreach all monday elements you have (for that one monday), and append the time to times and temp to temps.
-
I found a solution on gui. With this model I can fillgui correctly:
ListModel { id: model ListElement { name: "monday" attributes: [ ListElement { myWidth: 216.6 myColor: "red" myTemp: "29°" myTime: "0:00" }, ListElement { myWidth: 66.66 myColor: "red" myTemp: "26°" myTime: "6:30" }, ListElement { myWidth: 283.33 myColor: "red" myTemp: "30°" myTime: "8:30" }, ListElement { myWidth: 166.66 myColor: "red" myTemp: "26°" myTime: "17:00" }, ListElement { myWidth: 66.66 myColor: "red" myTemp: "29°" myTime: "22:00" } ] } ListElement { name: "tuesday" attributes: [ ListElement { myWidth: 216.6 myColor: "red" myTemp: "29°" myTime: "0:00" }, ListElement { myWidth: 66.66 myColor: "red" myTemp: "26°" myTime: "6:30" }, ListElement { myWidth: 283.33 myColor: "red" myTemp: "32°" myTime: "8:30" }, ListElement { myWidth: 166.66 myColor: "red" myTemp: "26°" myTime: "17:00" }, ListElement { myWidth: 66.66 myColor: "red" myTemp: "29°" myTime: "22:00" } ] } ListElement { name: "wednsday" attributes: [ ListElement { myWidth: 216.6 myColor: "red" myTemp: "29°" myTime: "0:00" }, ListElement { myWidth: 66.66 myColor: "red" myTemp: "26°" myTime: "6:30" }, ListElement { myWidth: 283.33 myColor: "red" myTemp: "32°" myTime: "8:30" }, ListElement { myWidth: 166.66 myColor: "red" myTemp: "26°" myTime: "17:00" }, ListElement { myWidth: 66.66 myColor: "red" myTemp: "29°" myTime: "22:00" } ] } }
And this is delegate:
Component { id: myRectComp; Item { width: 800; height: 90 ColumnLayout { id: scheduleList Label { id: weekday color: 'white' text: name font.pixelSize: 10 font.bold: true } RowLayout { spacing: 2 Repeater { model: attributes ColumnLayout { Rectangle { width: myWidth height: 50 color: myColor Label { anchors.centerIn: parent color: 'black' text: myTemp font.pixelSize: 12 font.bold: true } } Rectangle { width: myWidth height: 40 color: '#000' Label { color: 'white' text: myTime font.pixelSize: 9 font.bold: true } } } } } } }
But only miss part on javascript for fill by json this listModel.
I think that will be something like this:
fruitModel.append(..., "attributes": [{"myWidth":216,"myTemp":"29°"}, {"myWidth":66,"myTemp":"30°"}]);
Now try and keep inform if works. For now thanks.
-
That was sort of the same i sent you, or exactly the same actually..
So just parse the data as i described and it'll work fine :)
-
daysModel.append({
"day": "new day",
"times": [{"description": "00:00"}, {"description": "06:00"}],
"temps": [{"description": "17"}, {"description": "21"}]
})Works fine on mine. So yours should be ok.
Good work :)
-
I found a solution: edit component at this way:
Component.onCompleted: { var weekdays = ["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]; var xhr = new XMLHttpRequest; xhr.open("GET", constants.baseURL + "/api/thermostat.php?zone=1&season=summer"); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { var json = JSON.parse(xhr.responseText); model.clear(); var list = json["daily_schedule"]; for (var i in list) { var day=[]; if (json.daily_schedule[i]) { for (var k in json.daily_schedule[i].times_of_operation) { var start = json.daily_schedule[i].times_of_operation[k].start; var stop = json.daily_schedule[i].times_of_operation[k].stop; var temp = json.daily_schedule[i].times_of_operation[k].temp; // If json file schedule contain daily schedule, get start, stop and temperature of each period and zone if (json.daily_schedule[i].weekday === weekdays[i]) { var rowDay = { myWidth: ((Schedule.decode_time(stop) - Schedule.decode_time(start)) / 24) * 800, myColor: Schedule.color_map(temp, 'summer'), myTemp: temp, myTime: start }; day.push(rowDay); } } model.append({ "weekday": list[i]["weekday"], "attributes": day }) } } } } xhr.send(); }
Thank you very much.
Now I have others problem tobesolved, but for now I resolve this and thank you.
-
I'm trying to edit the created listView, in particular the attributes part. In particular I want to delete a period and then change the size of the previous one.
Basically I open a new page where I show the single day with the list of the periods of that day, and from there I can call the function that removes the period, and on that page everything seems ok.
When I close the page, however, in the main one I don't see the updated model. I see the period removed (even if sometimes it does strange things and it doesn't even do that) and the size is not updated (also here sometimes it does and sometimes it doesn't)
The period removal code is this:
function removePeriod(indexWeekday, weekday, index) { var modelData = model.get(indexWeekday); var newWidth = modelData.attributes.get(index - 1).myWidth + modelData.attributes.get(index).myWidth var newStop = modelData.attributes.get(index).myStop; console.log("Index Weekday: " + indexWeekday) console.log("Weekday: " + weekday) console.log("Index: " + index) console.log("New Width: " + newWidth) console.log("New Stop: " + newStop) modelData.attributes.remove(index); modelData.attributes.set(index - 1, {myWidth: newWidth}); modelData.attributes.set(index - 1, {myStop: newStop}); var day = []; for (var i = 0; i < modelData.attributes.count; i++) { day.push(modelData.attributes.get(i)); } model.set(indexWeekday, {"weekday": weekday, "attributes": day}) scheduleSinglePeriod.remove(index); scheduleSinglePeriod.set(index - 1, {"myWidth": newWidth}); }
Thank you
-
I resolved replaced model.set with these 2 lines:
model.insert(indexWeekday, {"weekday": weekday, "attributes": day}) model.remove(indexWeekday + 1)
I don't know if this code is clear and if it is a good way for update model.
I insert day row correctly on correct index sometimes I remove a period, and after remove next index, that is old day duplicated.
Thanks