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.
Related
Here's my QML view:
// Imports ommitted
Item {
id: paymentMethods
required property PaymentMethodsModel model
ColumnLayout {
anchors.fill: parent;
Text {
text: "Payment methods";
}
ListView {
Layout.fillHeight: true
Layout.fillWidth: true
model: paymentMethods.model
delegate: PaymentMethods.Item { }
}
ToolBar { }
}
}
The problem is, it looks like this:
I think it's because the delegate doesn't specify width, because if I do this:
delegate: PaymentMethods.Item {
width: parent.width
onPmSaved: {
ListView.view.model.rename(index, newName)
}
}
It looks much better:
The problem is, when I do edits that reorder the items, I get this error:
qrc:/PaymentMethods.qml:32: TypeError: Cannot read property 'width' of null
Is there a good way to set a QML ListView's delegate's width to full parent's width?
From the ListView documentation:
Delegates are instantiated as needed and may be destroyed at any time. As such, state should never be stored in a delegate. Delegates are usually parented to ListView's contentItem, but typically depending on whether it's visible in the view or not, the parent can change, and sometimes be null. Because of that, binding to the parent's properties from within the delegate is not recommended. If you want the delegate to fill out the width of the ListView, consider using one of the following approaches instead:
ListView {
id: listView
// ...
delegate: Item {
// Incorrect.
width: parent.width
// Correct.
width: listView.width
width: ListView.view.width
// ...
}
}
In the transition of the reordering, the item does not have a parent, so the error indicates it, a possible solution is to set the width of the item depending on whether it has a parent or not.
width: parent ? parent.width : 40 // default value
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
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;
}
}
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.
I created a ListView, which displays a couple of pages of content defined by the user (plain text). The page displayed is a delegate. Only one page is visible at a time. I decided to use it to get snapping to one item, in the same way the iOS' launcher works. The user simply flicks between the pages. (this is to be used on touch screens)
I need to have the index of the currently displayed page for some operation. currentIndex of the ListView always stays == 0. How can I get it?
For those who prefer code:
ListView
{
onCurrentIndexChanged: console.log(currentIndex) // this gets called only once - at startup
delegate: Column
{
// The page displayed, only one page at a time
}
}
Thanks
There are many ways to get the index of current item that is displayed in the screen. If you can get the x-y coordinate of current page, you can use indexAt method in ListView.
And in each delegate, you can find the index using index role within the scope of the delegate. The index is like a role you declared in your model, and is automatically assigned by ListView. For example,
ListView
{
delegate: Column
{
property int indexOfThisDelegate: index
//...
}
}
The index role is introduced here:
A special index role containing the index of the item in the model is also available to the delegate. Note this index is set to -1 if the item is removed from the model...
Another way is to explicitly assign value to the currentItem property in ListView, so the view can scroll by itself. Here is an simple example in Qt documentation, which is similar to your application.
I know this is quite old but I had the same problem and spend some time trying to find a way to get currentIndex that would work for me. In my case sometimes I need to change the width of my ListView so I have to recalculte currentIndex manualy every time I resize it.
But I found a highlightRangeMode property. When it's set to ListView.StrictlyEnforceRange then currentIndex is always updated automaticly and contains correct index of the currently visible item.
ListView {
highlightRangeMode: ListView.StrictlyEnforceRange
// ...
}
You can do like that:
QModelIndex index =this->indexAt(event->pos());
this ->setCurrentIndex(index);
You can use attached properties of ListView class (ListView). They are attached to each instance of the delegate.
See ListView.isCurrentItem or ListView.view example:
ListView {
width: 180; height: 200
Component {
id: contactsDelegate
Rectangle {
id: wrapper
width: 180
height: contactInfo.height
color: ListView.isCurrentItem ? "black" : "red"
Text {
id: contactInfo
text: name + ": " + number
color: wrapper.ListView.isCurrentItem ? "red" : "black"
}
}
}
model: ContactModel {}
delegate: contactsDelegate
focus: true
}