QML GridView contentItem children lenght doesn't match count - qt

I'm experiencing issues with a GridView dinamically populated in a QtQuick app I'm developing.
The GridView is populated by the user input of a number of identical objects, except for some text.
GridView {
id: grid_fc
width : parent.width
height: parent.height / 2
model: ListModel {}
delegate: Fancoil {
objectName: nome
text: indirizzo
}
}
onNewNode:
{
grid_fc.model.append({nome: "fc_" + address, indirizzo: address})
}
At a certain signal the app need to change a property of these objects, so I search for each object by objectName and do the job.
onStatusChanged:
{
for(var i = 0; i <= grid_fc.count; i++)
{
if (grid_fc.contentItem.children[i].objectName === "fc_" + address)
{
if (online)
grid_fc.contentItem.children[i].status(Fancoil.Status.ONLINE)
else
grid_fc.contentItem.children[i].status(Fancoil.Status.OFFLINE)
break
}
}
}
If the number of object is "low" the GridView.count is equal to the GridView.contentItem.children lenght, but increasing the number, I get that the GridView.count it's correct, instead of the children lenght that is wrong.
Ex. Populating the GridView with 100 elements:
GridView.count = 100
GridView.contentItem.children = 74 (?!)
What's wrong with my code?

If the model has 100k elements, it is inefficient for the view to create 100k items if it is only going to show 100. In other words, the view dynamically creates the necessary items, so it is not recommended to access those elements through the "childrens", since these can change. Instead you should use the models:
ListModel{
id: gridview_model
}
GridView {
id: grid_fc
width : parent.width
height: parent.height / 2
model: gridview_model
delegate: Fancoil {
text: indirizzo
}
}
onNewNode:
{
gridview_model.append({status: Fancoil.Status.OFFLINE, indirizzo: address})
}
onStatusChanged: {
for(var i = 0; i <= gridview_model.count; i++)
{
var new_status = online ? Fancoil.Status.ONLINE : Fancoil.Status.OFFLINE
gridview_model.get(i).status = new_status
}
}

I get the point, but I tried this way and the result is the same.
Now, the model count is correct and I can see the code looping through all the nodes.
It seems like a rendering issue. Images out of sight seem not to catch the status update.
Also this happens: scrolling the gridview up and down causes some nodes to lose the red glow (status update to offline).
See the result of first time render

Related

Qt QML Notify ListView when items in the model are added or removed

My ListView should always scroll its currently active item to the top between preferredHighlightBegin and preferredHighlightEnd. That works while clicking an item. However when a new item is added to my AbsctractListModel (And its not just added on the end but could be added anywhere in the middle), the ListView shows the new item, but it is not scrolled to the top. This is most likely because list.currentIndex = index is not set for new added items. I want to set it but did not find a way yet to do so. Does anyone have a hint?
I tried different signals such as onCurrentItemChanged but did not find the proper one yet which also gives me the correct index.
ListView {
id: list
anchors.fill: parent
clip: true
spacing: 11
focus: true
highlightMoveDuration: 400
snapMode: ListView.SnapToItem
preferredHighlightBegin: 0
preferredHighlightEnd: 100
highlightRangeMode: ListView.StrictlyEnforceRange
model: myAbstractListModel
delegate: ListItem {
height: 56
width: list.width
color: "red"
onListItemClicked: {
list.currentIndex = index
}
}
}
The expected result would be a ListView which scrolls a programmatically new added item of my AbstractListModel to the top (between preferredHighlightBegin and preferredHighlightEnd).
Adding this to your ListView will change the current index to the last one whenever the number of items in the model changes, you might want to keep track of the previous items count if you want to scroll only on insertion.
onCountChanged: {
if (count > 0) {
currentIndex = count - 1;
}
}
If the new items might be inserted anywhere in the model you could connect directly to it using the Connections item inside your ListView and connect it directly to the rowsInserted signal of QAbstractItemModel.
Connections {
target: model
onRowsInserted: {
currentIndex = first + 1;
}
}
If your model emits only the dataChanged signal you can try this.
Connections {
target: model
onDataChanged: {
currentIndex = topLeft.row() + 1;
}
}

Add elements to a ListView inside another ListView

I need to insert elements in a ListView inside another ListView (via JS code inside my QML file) but when I try to access the inner ListView I get the error :
TypeError: Cannot call method 'insert' of undefined
Here is an example code to show my problem :
Item{
id:list
width: parent.width-210
height: parent.height
x:105
Component{
id:listDelegate
Item {
id:elem
height: 100
width: parent.width
Item{
id:titre_liste
height: 50
width: parent.width
Text{
anchors.left: parent.left
color:"white"
text:titre_txt
font.pixelSize: 25
font.bold: false
}
}
Item{
id:listInList
width: parent.width-100
height: parent.height
Component{
id:listInListDelegate
Item{
id:element_liste
height: parent.height
width: parent.width/5
Text{
anchors.left: parent.left
color:"white"
text:element_txt
font.pixelSize: 25
font.bold: true
}
}
}
ListView {
id: viewin
anchors.fill: parent
model: ListModel{
id:listModel_in
}
delegate: listInListDelegate
}
}
}
}
ListView {
id: viewglobal
anchors.fill: parent
model: ListModel{
id:listModel
}
delegate: listDelegate
}
}
And here is my JS code, at the end of the QML file :
function addItem(){
var i;
var numListe = -1;
var liste = "titre"
var item = "item"
for(i = 0;i<listModel.count;i++)
{
if(listModel.get(i).titre_txt === liste)
{
numListe = i;
}
}
if(numListe === -1)//if the list doesn't exist
{
listModel.append({titre_txt:liste});
numListe = listModel.count-1;
}
listModel.get(numListe).listModel_in.insert(0,{element_txt:item});
}
The error come from the last line of the JS code, when I try to insert a new element in the inner list. I verified that the value of "numListe" is 0 so it is not just a problem of wrong index.
How can I add elements to the inner list ?
There is a lot of stuff wrong with that code.
For starters - it is a mess, which is a very bad idea for someone who is obviously new at this stuff. Keep it clean - that's always a good idea regardless of your level of expertise.
listModel_in is an id and as such cannot be accessed outside of the delegate component.
That object however happens to be bound to the view's model property, so as long as the model doesn't change, you can access listModel_in via the model property. However, the view itself doesn't look like it is the delegate root object, so you have to interface it, for example by using an alias.
However, the inner model doesn't exist in the outer model, it only exists in the outer model's delegate item.
So you cannot possibly get it from listModel. You can get it from the viewglobal view, however ListView doesn't provide access by index. So you will have to set currentIndex for every index and use currentItem.
So it will look like this:
viewglobal.currentItem.modelAlias.insert(0,{element_txt:item});
But it should go without saying, you are putting data in the GUI layer, which is conceptually wrong. But it gets worse than conceptually wrong - you might not be aware of this, but ListView only creates items that it needs to show, meaning that it creates and destroys delegates as necessary. Meaning if your item falls out of view, it will be destroyed, and when it comes back into view, a new one will be created, and all the data you had in the model of the old delegate item will be lost. The view should never store data, just show it.
The inner model should be inside the outer model. However, last time I checked, QMLs ListModel didn't support model nesting, neither using declarative nor imperative syntax. If you want to nest models, I have provided a generic object model QML type you can use.

Unique id for each element of a ListView

I have a ListView of Items and I need that each element has an unique id.
But I have two conditions on my Items:
I can't use index because my Itemscan be moved (drag&drop), so their index will change.
Each Item can be displayed many times, so there is no property of these elements that can help me to distinguish them.
My idea was to do something like that:
ListView {
id: list
property int uniqueId: 0
width: 180; height: 200
model: myModel
delegate: Text {
property int uniqueid: list.uniqueId
text: uniqueid
}
Component.onCompleted: list.uniqueId++
}
But it doesn't work because when list.uniqueId is updated, it will update my Items id and they will all have the same id ( id = list.uniqueId)
How could I proceed?
Well, if you severe (or don't create) the binding, it will not auto update:
delegate: Text {
Component.onCompleted: text = uniqueId++
}
However, I don't think avoiding this will really fix your design. What you should do is have the the unique id part implemented all the way back into the model data.

Bidirectional Binding of mutually dependent Properties in QML

While learning QML I stumbled over the problem, that I have to properties that are mutually dependent.
E.g. the user may set a value with a slider, or enter it with a textinput.
While moving the slider, the text in the textinput should be updated while when entering a value in the textinput, the sliders position needs to be adjusted.
Now I have the two properties: the x-value of the slider, and the text in the textinput. I need to convert them into the same format (for example: percent), and update them vice versa.
Setting up two bindings would result in a binding loop, which is probably not a good thing to have.
I think, this is a very common problem, so I am sure there is some "gold standard" to solve it. However I can't find a suitable solution.
The only way that comes to my mind, is not to use bindings at all, but handling the signals, that one of the values has changed manually (if I can't overwrite the setter in C++).
Is that all you can do?
Good day to you!
-m-
EDIT:
I now tried it with conditionally binding the value of the slider to the percentage value.
The object handle is the marker on the slider, handleArea is the MouseArea attached to it, that allows the dragging.
Binding {
target: root
property: 'percent'
value: handle.x / handleBar.width
when: handleArea.drag.active
}
Binding {
target: handle
property: 'x'
value: root.percent * handleBar.width
when: !handleArea.drag.active
}
It works. But is it a good style?
I would make one property that will store the mutual value. When user inputs text or moves slider they will use a function to update the mutual value. They will also listen to the value change and adjust their value to it.
Here is working example
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: window
visible: true
width: 400
height: 80
title: "Mutual value test"
property double mutualValue: 0
function setMutualValue(value)
{
if (typeof(value) === "string")
{
value = value.replace(",", ".");
value = parseFloat(value)
}
mutualValue = value
}
TextInput {
width: 200
height: 40
validator: DoubleValidator {}
onEditingFinished:
{
focus = false
setMutualValue(text)
}
onFocusChanged:
{
if (text === inputHelp && focus)
text = ""
}
property alias mutualValue: window.mutualValue
onMutualValueChanged: setValue(mutualValue)
property string inputHelp
color: (text === inputHelp && !focus ? "grey" : "black")
function setValue(mutualValue)
{
inputHelp = mutualValue.toFixed(3)
text = inputHelp
}
}
Slider {
x: 200
width: 200
height: 40
onValueChanged: setMutualValue(value)
property alias mutualValue: window.mutualValue
onMutualValueChanged: setValue(mutualValue)
function setValue(mutualValue)
{
value = mutualValue
}
}
Text {
x: (parent.width - width) / 2
y: 40 + (40 - height) / 2
text: "Current value is: " + (window.mutualValue * 100).toFixed(2) + "%"
}
}

QML signal executed twice

I'm new in QML and QML signals and I'm having this silly problem that I'm not being able to resolve by myself. I'm triggering an onTouch signal and is executing twice, generating a double response that crashes my app.
Here's my QML code:
//LabelKey.qml
import bb.cascades 1.0
Container {
property string labelText: "#"
property real width: 153.3
property real height: 102.5
property int labelPosX: 60
property int labelPosY: 25
property int labelTextFontWidth: 45
property string imgSrc: "asset:///images/keyboard_button.png"
layout: AbsoluteLayout {
}
preferredWidth: width
preferredHeight: height
objectName: "contTecla"
id: contTecla
ImageView {
objectName: "imgTecla"
id: imgTecla1
imageSource: imgSrc
preferredWidth: width
preferredHeight: height
onTouch: {
textFieldKey.text = textFieldKey.text + labelTecla.text;
}
}
Label {
objectName: "labelTecla"
id: labelTecla
text: labelText
textStyle {
color: Color.DarkYellow
size: labelTextFontWidth
}
layoutProperties: AbsoluteLayoutProperties {
positionX: labelPosX
positionY: labelPosY
}
}
}
I have this TextField whose id is textFieldKey in another QML where I'm including the one I post above. The main idea is simple, is a keyboard where each key is a component of the code above and it has to print the value of the key pressed in this TextField.
The problem is, as I said, the signals is being called twice, filling the TextField with two chars of the value each time.
Please help me I don't know if maybe I'm missing something in the proper way of using signals or something like that.
Thanks!
I figure it out. The touch signals has 4 differents states:
Down: Occurs when the user touches the screen.
Move: Occurs when the user moves a finger on the screen.
Up: Occurs when the user releases a finger.
Cancel: Occurs when an interaction is canceled.
Each one identify with a number from 0 to 3.
And when a touch signal is triggered two states are involved, Down and Up. You just need to make sure with wich one you want to work with and catch it inside the onTouch signal:
if (event.touchType == numberOfTheTouchState){
}
You want to use
ImageView
{
objectName: "imgTecla"
id: imgTecla1
imageSource: imgSrc
preferredWidth: width
preferredHeight: height
onTouch:
{
if(event.isDown())
{
textFieldKey.text = textFieldKey.text + labelTecla.text;
}
}
}
As was noted, without this you get both the up and down events

Resources