QML XmlListModel messes sorting when delegated - qt

I'm trying to get a Canvas to draw lines in the same order as presented in this xml-file:
<root>
<doc><nopeus>80.0</nopeus><aika>40.0</aika></doc>
<doc><nopeus>110.0</nopeus><aika>80.0</aika></doc>
<doc><nopeus>120.0</nopeus><aika>120.0</aika></doc>
<doc><nopeus>190.0</nopeus><aika>160.0</aika></doc><doc><nopeus>243.0</nopeus><aika>200.0</aika></doc><doc><nopeus>260.0</nopeus><aika>240.0</aika></doc><doc><nopeus>300.0</nopeus><aika>280.0</aika></doc><doc><nopeus>350.0</nopeus><aika>320.0</aika></doc>
</root>
QML-file with XmlListModel:
import QtQuick 2.0
import Sailfish.Silica 1.0
import QtQuick.XmlListModel 2.0
Page {
id: page
property alias startx : coords.mX
property alias starty : coords.mY
Item {
id: coords
property int mX: 0
property int mY: 0
}
XmlListModel {
id: myxml
source: "/home/nemo/filename.xml"
query: "/root/doc"
XmlRole { name: "nopeus"; query: "nopeus/string()" }
XmlRole { name: "aika"; query: "aika/string()" }
}
ListView {
model: myxml
anchors.fill: page
delegate:
Item {
Chart {
xc: coords.mX;
yc: coords.mY;
xd: aika;
yd: nopeus;
}
}
}
}
Chart.qml:
import QtQuick 2.0
Rectangle {
id: myrect
width: 540
height: 960
color: "transparent"
property int xd: 0
property int yd: 0
property int xc: 0
property int yc: 0
Canvas {
id: mycanvas
width: myrect.width; height: myrect.height;
onPaint: {
var context = getContext('2d')
context.strokeStyle = "#FF0000"
context.lineWidth = 2
context.beginPath()
context.moveTo(xc,yc)
context.lineTo(xd,yd)
context.stroke()
startx = xd
starty = yd
}
}
}
The question is why is the resulting path messed up when inserted in the ListView via delegates? I have tried to sort the path items separately, via a function and another ListModel but the result is the same.
Here is a screenshot:

Delegates are created for each item in a model. Your model contains eight items (as of your input). Hence, you create eight Canvases (each one as a ListView item, i.e. at (theoretical) increasing y w.r.t. ListView origin coordinates).
Combile these problems with the (probably wrongly set) starting points...and you get a random mess! You can't see that, since the Canvases tend to overlap due to sizing/constraints set on the component.
In this case you just need one Canvas on which each myxml item is painted. Here is a (naive) adaptation of your code which correctly shows the path stored in the xml file:
// main.qml
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.XmlListModel 2.0
Window {
visible: true
width: 600
height: 600
XmlListModel {
id: myxml
source: "qrc:/filename.xml" // added to the resources
query: "/root/doc"
XmlRole { name: "nopeus"; query: "nopeus/string()" }
XmlRole { name: "aika"; query: "aika/string()" }
onStatusChanged: {
if(status === XmlListModel.Ready)
comp.mod = myxml // set the model ASA it is ready to be used
}
}
Chart {
id: comp
anchors.fill: parent
mod: myxml
}
}
// Chart.qml
import QtQuick 2.4
import QtQuick.XmlListModel 2.0
Item {
property var mod: undefined
onModChanged: {
if(mod)
mycanvas.requestPaint() // repaint only when the model is available
}
Canvas {
id: mycanvas
width: parent.width; height: parent.height;
onPaint: {
var context = getContext('2d')
context.strokeStyle = "#FF0000"
context.lineWidth = 2
context.beginPath()
context.moveTo(0,0)
// iterate over all the point to print them
for(var i = 0; i < mod.count; i++)
{
var point = mod.get(i)
context.lineTo(point.aika, point.nopeus)
}
context.stroke()
}
}
}
The resulting path rendered:

Related

How to access data of QSqlQueryModel from a QML ListView delegate?

I want to access data by a query in SQL database and present its results as list of elements in QML. However, I'm not sure if I'm accessing this data in the right way. Or maybe QSqlQueryModel presents its data in unusual way? How can I check this?
Here's code from my QML file:
import QtQuick 2.0
import QtQuick.Controls 2.1
import QtQuick.Window 2.1
import QtQuick.Layouts 2.0
import QtQml.Models 2.3
import DbInterface 1.0
ApplicationWindow {
id: main
width: 400
height: 800
visible: true
Component {
id: appCentralListDelegate
Row {
Text {
text: signature
}
Text {
text: name
}
}
}
DbInterface { id: appCentralListModel }
DelegateModel {
id: delegateModel
property var isSearchResult: function(item) {
return true;
// not implemented yet
}
model: appCentralListModel
delegate: appCentralListDelegate
function update() {
console.log("Starting update");
if (items.count > 0) {
items.setGroups(0, items.count, "items");
}
// Step 1: Filter items
var visible = [];
for (var i = 0; i < items.count; ++i) {
var item = items.get(i);
if (isSearchResult(item.model)) {
visible.push(item);
}
}
// Step 3: Add all items to the visible group:
for (i = 0; i < visible.length; ++i) {
item = visible[i];
item.inVisible = true;
if (item.visibleIndex !== i) {
visibleItems.move(item.visibleIndex, i, 1);
}
}
}
items.onChanged: update()
onIsSearchResultChanged: update()
groups: DelegateModelGroup {
id: visibleItems
name: "visible"
includeByDefault: false
}
filterOnGroup: "visible"
}
ListView {
id: appCentralListView
anchors.fill: parent
model: delegateModel
spacing: 0
}
Row {
TextField {
id: searchField
}
}
}
And here is a file where DbInterface is presented:
from PySide6.QtSql import QSqlQueryModel
from PySide6.QtQml import QmlElement
QML_IMPORT_NAME = "DbInterface"
QML_IMPORT_MAJOR_VERSION = 1
#QmlElement
class DbInterface(QSqlQueryModel):
def __init__(self, parent=None):
super(DbInterface, self).__init__(parent)
self.setQuery(open("query.sql").read())
if self.lastError().isValid:
print(self.lastError())
else:
print("Query executed successfully.")

Dynamically bind values from qml to repeater created object

I control the position of some elements of my scene using alias properties likes this : If I have a file Foo.qml containing
Item {
property alias myprop1: id1
property alias myprop2: id2
Node {id:id1,...}
Node {id:id2,...}
On my main, I can then call
Slider{
id:myslider
}
foo{
myprop1.x: myslider.value
}
Now if my Foo.qml contains an unknow number of properties (lets say they are all called mypropX). If I have 10 properties I want to create 10 sliders, one for each property. It is possible with a repeater and loop like mentioned in last answer here
Foo{
id:myfoo
}
Column {
Repeater {
id: myrepeater
delegate: Slider {
from:0
to:400
y: 12*index
}
Component.onCompleted: {
let propArray = [];
for(var prop in myfoo){
//select only the properties I'm interested in
//a "onXXXChanged" is created on each properties so I also have to remove it
if(prop.substring(0, 6)==="myprop" && prop.substring(prop.length-7,prop.length)!=="Changed"){
propArray.push(prop)
}
}
myrepeater.model = propArray
}
}
}
The problem is now that I don't know how to bind those 10 sliders to my properties.
I tried adding to my Foo instance in main
Component.onCompleted: {
let i=0
for(var prop in myfoo){
if(prop.substring(0, 6)==="myprop" && prop.substring(prop.length-7,prop.length)!=="Changed"){
//equivalent to myprop1.x: myslider.value when there was no repeater
myfoo.prop.x = Qt.binding(function() {
return myrepeater.itemAt(i).value
})
i++
}
}
}
But it return
QQmlEngine::setContextForObject(): Object already has a QQmlContext
qrc:/main.qml:145: Error: Cannot assign to non-existent property "prop"
The problem is that in the for loop, prop is a string. I am also not sure that at the moment the onCompleted is executed, the repeater has already created all the slidders.
I could use the QML type Bindings{} which takes a target (myrepeater.itemAt(i).value) and the property name as a string, but I don't know how to call the Bindings{} type from javascript
You can use the [] operator to read the properties from myfoo and as discussed I would use a Binding object inside the delegate:
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Item {
id: myfoo
property int myprop_upper_threshold
onMyprop_upper_thresholdChanged: console.log("upper_threshold", myprop_upper_threshold)
property int myprop_lower_threshold
onMyprop_lower_thresholdChanged: console.log("lower_threshold", myprop_lower_threshold)
}
ColumnLayout {
Repeater {
id: myrepeater
delegate: Slider {
id: myslider
from: 0
to: 400
Text {
text: modelData
}
Binding {
target: myfoo
property: modelData
value: myslider.value
}
}
Component.onCompleted: {
let propArray = [];
for(var prop in myfoo)
{
//select only the properties I'm interested in
//a "onXXXChanged" is created on each properties so I also have to remove it
if(prop.substring(0, 6)==="myprop" && prop.substring(prop.length-7,prop.length)!=="Changed")
{
propArray.push(prop)
}
}
myrepeater.model = propArray
}
}
}
}

Property alias cannot be accessed through another QML

i was trying to make an app with Local Storage for the database and i want to display the data from the database to a listview in different QML file. i know i can make a property alias to the listview so i can access it from another file then append the query result. but when i hit the button to show the data it says that listViewKos was not define but it was already property alias
i did my best to keep the code short, some of the component might have been deleted due to it but appart from the problem i describe above everything works just fine.
#main.qml the JS is just a JS object where the data came from
import QtQuick 2.5
import QtQuick.Controls 2.5
import QtQuick.Dialogs 1.1
import QtQuick.LocalStorage 2.0
import "./Storage.js" as Storage
ApplicationWindow {
id: applicationWindow
width: 640
height: 480
property int kontrakanJumlahKamar
property int kontrakanPrice
property int kosPrice
property string kosGenderType
property alias kosloader: kosloader
property var db
Component.onCompleted: {
db = LocalStorage.openDatabaseSync("ngomahyuk", "1.0", "StorageDatabase", 1000000)
db.transaction(function(tx){
tx.executeSql('CREATE TABLE IF NOT EXISTS kos(namakos TEXT, alamat TEXT, thumbnail TEXT)');
});
// insert data for kos
db.transaction(function(tx){
for (var i = 0; i < Storage.kos.length; i++){
try {
tx.executeSql("INSERT INTO kos (namakos, alamat, thumbnail) VALUES('"+Storage.kos[i].namakos+"','"+Storage.kos[i].alamat+"','"+Storage.kos[i].thumbnail+"'");
} catch (err) {
console.log(err);
}
}
});
}
Button {
id: button
text: qsTr("Search")
MouseArea{
anchors.fill: parent
onClicked:{
if (textFieldHarga.text === ""){
kosPrice = 0
} else {
kosPrice = parseInt(textFieldHarga.text)
}
kosGenderType = comboBoxGender.currentText
kosloader.visible = true
db.transaction(function(tx){
var rs = tx.executeSql("SELECT * FROM kos WHERE gender = '"+kosGenderType+"' AND harga <= "+kosPrice);
if (rs.rows.length === 0){
alertDialogKos.open()
}else {
for (var i = 0; i < rs.rows.length; i++){
listViewKos.model.append({ //this is the one that suppose to be working
imagePath : rs.rows[i].thumbnail,
kosName : rs.rows[i].namakos,
kosAlamat : rs.rows[i].alamat,
})
}
kosloader.source = "Kos.qml"
}
});
}
}
MessageDialog{
// messagedialog code deleted to keep it short
}
background: Rectangle {
// deleted to keep it short
}
contentItem: Text {
id: textItem
text: "Search"
}
}
Loader{
id: kosloader
width: 640
height: 480
opacity: 1
clip: false
visible: false
active: false
anchors.fill: parent
source: ""
}
}
#Kos.qml i use PageBackground.qml as a background
import QtQuick 2.4
import QtQuick.Controls 2.3
PageBackground {
id: kos
width: 640
height: 480
property alias listViewKos: listViewKos
ListView {
id: listViewKos
x: 15
y: 87
width: 640
height: 410
clip: true
model: ListModel{
// need to be in for loop and data from database
}
// delegate listview template
delegate: Item {
height : 195
width : 640
Image {
id: idthumbnail
width: 235
height: 165
source: imagePath
}
Text {
id: idnamakos
x: 252
y: 8
text: kosName
}
Text {
id: idalamat
text: qsTr("Alamat : " + kosAlamat)
}
}
}
}
}

QML GridView hide one cell

It's possible to hide certain cell in GridView? I set delegate, but I still got empty place for this GridView element. It's possible to do this?
visible: false
width: 0
height: 0
As was said in the comment, you can indeed use a QSortFilterProxy model, but here is another solution. You could implement a pure-QML FilterProxyModel, using DelegateModel and DelegateModelGroup
import QtQuick 2.10
import QtQml.Models 2.3
DelegateModel {
property var filterAccepts: function(item) {
return true
}
onFilterAcceptsChanged: refilter()
function refilter() {
if(hidden.count>0)
hidden.setGroups(0, hidden.count, "default")
if(items.count>0)
items.setGroups(0, items.count, "default")
}
function filter() {
while (unsortedItems.count > 0) {
var item = unsortedItems.get(0)
if(filterAccepts(item.model))
item.groups = "items"
else
item.groups = "hidden"
}
}
items.includeByDefault: false
groups: [
DelegateModelGroup {
id: default
name: "default"
includeByDefault: true
onChanged: filter()
},
DelegateModelGroup {
id: hidden
name: "hidden"
}
]
}
Explanation: Every time an item is added to the model, it is added in the "default" group, which triggers the onChanged handler that will call filter().
Filter() will look for items in the default group, and move them either in the items group (which will make them visible) or to the hidden group, depending on the result of the filterAccepts function.
When filterAccept changes, the SortProxyModel will move every item to the default group to trigger a global refiltering.
You can then use your proxy model like this:
FilterProxyModel
{
id: filterProxyModel
model: <YourBaseModel>
delegate: <YourDelegate>
filterAccepts: function(item) {
// Eg: Only "small" items will be displayed
return item.size == "small"
}
}
GridView
{
anchors.fill: parent
model: filterProxyModel
cellHeight: 100
cellWidth: 100
}
Another simplified solution with QML only, based on hiding items.
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Layouts 1.2
Window {
id: window
title: "test"
visible: true
width: 400
height: 400
GridLayout {
id: layout
anchors.fill: parent
columns: 4
Repeater {
id: container
model: 20
Rectangle {
id: item
property int itemIndex: index
Layout.fillWidth: true
height: 60
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1)
Text {
anchors.centerIn: parent
text:item.itemIndex
}
MouseArea {
anchors.fill: parent
onClicked: {
item.visible = false;
layout.doIt(item.itemIndex);
}
}
}
}
function doIt(index)
{
var item = container.itemAt(index);
if(item)
item.visible = false;
for(var i = index - 1;i >= 0;i --)
{
var prev_item = container.itemAt(i);
if(prev_item.visible) {
prev_item.Layout.columnSpan ++;
break;
}
}
}
}
}

Dynamically create QML ListElement and content

So I am trying to dynamically create ListElements in a ListModel. This works fine until I try writing some content in the ListElements to be loaded dynamically.
I tried making an own file with the ListElement within and the hour as a property, but the model then I got an error saying that ListElements can not be nested.
The error for running the code below is:
Cannot assign to non-existent property "hour"
How can I solve this?
Code:
import QtQuick 2.0
ListModel
{
id: listModel
Component.onCompleted:
{
for (var i = 0; i < 24; i++)
{
var object = createListElement(listModel)
}
}
function createListElement(parent)
{
var object = Qt.createQmlObject('import QtQuick 2.0; ListElement { hour: "01" }', parent);
return object;
}
}
EDIT:
Change the code line in the function to:
var object = Qt.createQmlObject('import QtQuick 2.0; ListElement { property string hour: "23" }', parent);
Now I get no errors, but the elements are still not showing in the list.
I'm not sure why that doesn't work, but using plain old JavaScript objects does the job:
import QtQuick 2.4
import QtQuick.Window 2.0
Window {
width: 400
height: 400
ListView {
id: listView
anchors.fill: parent
model: listModel
delegate: Rectangle {
width: listView.width
height: listView.height / 4
Text {
text: hour
anchors.centerIn: parent
}
}
}
ListModel {
id: listModel
Component.onCompleted: {
for (var i = 0; i < 24; i++) {
append(createListElement());
}
}
function createListElement() {
return {
hour: "01"
};
}
}
}

Resources