Foreach delegate in QML view - qt

Is it possible to iterate through the delegates of a ListView or GridView using foreach or a similar function?

While Simon's answer is a best practice, to answer the actual question being asked you need to iterate over the children of ListView's contentItem like so:
ListView {
id: list
model: mymodel
delegate: Text {
objectName: "text"
text: name + ": " + number
}
}
for(var child in list.contentItem.children) {
console.log(list.contentItem.children[child].objectName)
}
You can then filter using objectName or any other property of the delegate Item.

Are you sure you want to iterate over delegates? In most cases you want to iterate over the model because in case of a ListView there might be only a handful of delegates even if your model has 100 entries. This is because a delegate is re-filled when it moved out of the visible area.
You need a model that has a function like for example at() which returns the model element for a given position. Than you can do something like
ListView {
// ...
function find(convId)
{
// count is a property of ListView that returns the number of elements
if (count > 0)
{
for (var i = 0; i < count; ++i)
{
// `model` is a property of ListView too
// it must have an at() metghod (or similar)
if (model.at(i)["id_"] === convId)
{
return i;
}
}
}
}
// ...
}

Related

Individual IDs of Repeater child in QML

I was wondering if it is possible to assign individual IDs to the child of a repeater in QML?
For example, if my repeater was to create 10 rectangles, how do I assign each of them a unique id? e.g. rec1, rec2, etc.
As already said above id is not a regular property and cannot be set or changed at runtime. You cannot reference this attribute, for example the following code will not work:
console.log(obj.id);
The nearest analogue of id from C++ is a variable name:
auto id = new Object();
when you can reference the name but cannot set or change it.
As for the issue, you can reference an item using objectName, or by using some specified access function, for example:
Row {
id:row
anchors.centerIn: parent
spacing: 5
Repeater {
id: container
model: 10
Text {
text:"item" + index
objectName: "item" + index
}
Component.onCompleted: {
container.itemAt(5).text = "changed1";
findChild(row, "item6").text = "changed2";
}
function findChild(obj, name) {
for(var i = 0;i < obj.children.length;i ++){
if(obj.children[i].objectName === name)
return obj.children[i];
}
return undefined;
}
}
}
Just add this property to any QML object that requires a unique id:
readonly property string uniqueId: `${this}`.substring(`${this}`.indexOf("(0x") + 2).replace(")", "")

How to listen for property collection size changes

I want to automatically show/hide view depending on a property's collection size, here's the code:
QtObject {
property var controlWindow: Window {
property var collection: []
signal sigAddElement(var element)
onSigAddElement: {
collection.push(element)
}
signal sigEraseAllElements()
onSigEraseAllElements: {
collection.length = 0
}
onCollectionChanged: {
console.log("collection.len = " + collection.length)
}
Rectangle {
id: autoHidableView
visible: collection.length != 0
}
}
}
but visible property of autoHidableView evaluates only once on startup and never evaluates again
The onCollectionChanged handler is never get called which is understandable since collection object itself stays the same
So is it possible to listen for collection's size change event?
The problem is that the javascript array that you create with property var collection: [] does not have any signals (the onCollectionChanged is indeed when you would assign a new collection to it). You better use ListModel:
QtObject {
property var controlWindow: Window {
ListModel {
id: collection
}
signal sigAddElement(var element)
onSigAddElement: {
collection.append(element)
}
signal sigEraseAllElements()
onSigEraseAllElements: {
collection.clear()
}
Rectangle {
id: autoHidableView
visible: collection.count > 0
}
}
}
Note that you need to change the push to append
Listening to the array length is not enough.
See var QML Basic Type:
It is important to note that changes in regular properties of JavaScript objects assigned to a var property will not trigger updates of bindings that access them.
It is the same behavior when assigning an array to the var. The binding will only be reevaluated if the property is reassigned with an entire new object/array.
You have 2 ways to make your binding listen to the collection length change:
Reassign the entire array:
onSigAddElement: {
collection = collection.concat(element)
}
// ...
onSigEraseAllElements: {
collection = []
}
Trigger the change signal manually:
onSigAddElement: {
collection.push(element)
collectionChanged()
}
// ...
onSigEraseAllElements: {
collection.length = 0
collectionChanged()
}

Multiple items selection from ListView

I want to select multiple items from a ListView. In C++ I would have done something like this
if (clicked_card->is_selected) {
clicked_card->is_selected = false;
int i = 0;
while(selected_cards[i] != clicked_card) i++;
selected_cards.erase(selected_cards.begin() + i);
} else {
clicked_card->is_selected = true;
selected_cards.push_back(clicked_card);
}
The above code uses pointer for comparison. So how to so such selection in QML. The solution I've come up with is something like this
Card.qml
Image {
id: delegate
property bool isSelected: false
...
MouseArea {
onClicked: {
if(isSelected === true) {
isSelected = false;
gameScene.deselectCard(selectSeq);
}
else {
isSelected = true;
gameScene.selectCard({'opParam': opParam, 'selectSeq': selectSeq});
}
}
}
}
GameScene.qml
Item {
id: gameScene
property var selectedCards: []
signal selectCard(variant userData)
onSelectCard: {
gameScene.selectedCards.push(userData)
}
signal deselectCard(variant userData)
onDeselectCard: {
for (var i = 0; i < gameScene.selectedCards.length; i += 1) {
if (gameScene.selectedCards[i].selectSeq == userData) {
gameScene.selectedCards.splice(i, 1);
break;
}
}
}
}
The problem with the above code is that I'm storing property isSelected in a delegate which is created and destroyed by the system. So this is giving me false solution. Is there any better way of multiple selection or any improvements in the solution ? I'm using model from C++ by subclassing QAbstractListModel.
I found the answer in Qt documentation. I simply have to use [DelegateModel][1]. It has a group property, for every group defined in a DelegateModel two attached properties are added to each delegate item. The first of the form DelegateModel.in*GroupName* holds whether the item belongs to the group and the second DelegateModel.*groupName*Index holds the index of the item in that group.
import QtQuick 2.0
import QtQml.Models 2.2
Rectangle {
width: 200; height: 100
DelegateModel {
id: visualModel
model: ListModel {
ListElement { name: "Apple" }
ListElement { name: "Orange" }
}
groups: [
DelegateModelGroup { name: "selected" }
]
delegate: Rectangle {
id: item
height: 25
width: 200
Text {
text: {
var text = "Name: " + name
if (item.DelegateModel.inSelected)
text += " (" + item.DelegateModel.selectedIndex + ")"
return text;
}
}
MouseArea {
anchors.fill: parent
onClicked: item.DelegateModel.inSelected = !item.DelegateModel.inSelected
}
}
}
ListView {
anchors.fill: parent
model: visualModel
}
}
Other solution would have been to move the property isSelected to C++ data model and use a getter and setter function to update the changes.
An easy solution. Use QPair or QPair to store the state of all of your item.
typedef QPair<int, bool> ItemState;
Enable multiple selection in your list o table:
ui->tableView->setSelectionMode(QAbstractItemView::MultiSelection);
And when you want to select a collection, just try something like this:
QList<ItemState> collection;
foreach (ItemState& el , collection) {
const int row = el.first;
const bool state = el.second;
const QModelIndex& index = ui->tableView->model()->index(row, 0);
ui->tableView->selectionModel()->select(index, state ? QItemSelectionModel::Select : QItemSelectionModel::Deselect );
}
You should update your collection data, everytime you modify the data in the model (add, remove o move elements). When the user clicks in a card, just handle the clicked event and modify your collection item state, and recall the loop.

finding a specific element of a ListModel based on its properties in QtQuick 2.0

I have a ListModel with an objectId role (integer and unique). I want to query the model to find other properties of an element with a specific objectId. How can I do that?
You can loop other your model to retrieve each element one by one searching for your objectId
for(var i = 0; i < myModel.count; i++) {
var elemCur = myModel.get(i);
if(searchedId == elemCur.objectId) {
console.log("Found it at index : ", i);
}
}

QML : How to get current item in Pathview

I would like to know how to get the current item in the Pathview.
We do have have iscurrentItem property but how can we use it if its possible. With my present implementation I'm getting the current index but not the value of the item at that index
If you have the preferredHighlightBegin and preferredHighlightEnd properties defined you could use the childAt function to get the current item:
//example for vertical path
view.childAt(0, view.height * (preferredHighlightEnd + preferredHighlightBegin) / 2.0);
There is another approach where you can iterate all the children of the PathView and find which of the children is the current item:
for(var i = 0; i < view.children.length; ++i)
{
if(view.children[i].PathView.isCurrentItem){
console.log(i + " is current item")
//view.children[i] is your current item here
}
}
In your delegate Item you can set a Connection that updates a path custom property:
PathView {
id: myPathView
// ...
property Item currentItem
delegate: pathDelegate
}
Component {
id: pathDelegate
Item {
id: delegateItem
// ...
Connection {
target: myPathView
onCurrentIndexChanged: {
if (myPathView.currentIndex === delegateItem.index) {
myPathView.currentItem = delegateItem;
}
}
}
}
}
Not so efficient, but it works.

Resources