QML TreeView passes previous selection when clicking to collapse or expand - qt

I have a QML TreeView containing some onClicked() logic that calls a Q_INVOKABLE function that takes in the current row number and the parent row number of the TreeView as parameters. The problem is that when I select something, and then I click to expand or collapse something. The previous values are still getting passed which sometimes makes the application crash. I've tried to call treeView.selection.clearCurrentIndex() and treeView.selection.clearSelection() in onCollapsed() and onExpanded() which deselects the item, but for some reason still passes the values from the previously selected item.
//main.qml
TreeView {
id: treeView
anchors.fill: parent
model: treeviewmodel
selection: ItemSelectionModel {
model: treeviewmodel
}
TableViewColumn {
role: "name_role"
title: "Section Name"
}
onCollapsed: {
treeView.selection.clearSelection() // deselects the item, but still passes the previous values
}
onExpanded: {
treeView.selection.clearSelection()
}
onClicked: {
console.log("Current Row: " + treeView.currentIndex.row + "Parent Row: " + treeView.currentIndex.parent.row)
//I need something here that will set treeView.currentIndex.row and treeView.currentIndex.parent.row to -1
//so that when I collapse or expand, -1 gets passed instead of the previous values
}
}

I was able to solve this by setting some additional flags (thanks #Tarod for the help). I had to save the value of the rows so that I could check if they changed. If they did not change, I would not call the function, so no obsolete values would get passed.
TreeView {
id: treeView
anchors.fill: parent
model: treeviewmodel
property int currentRow: -1
property int parentRow: -1
property int lastCurrentRow: -1
property int lastParentRow: -1
selection: ItemSelectionModel {
model: treeviewmodel
}
TableViewColumn {
role: "name_role"
title: "Section Name"
}
onCollapsed: {
currentRow = -1
parentRow = -1
}
onExpanded: {
currentRow = -1
parentRow = -1
}
onClicked: {
console.log("Row: " + treeView.currentIndex.row + " Parent : " + treeView.currentIndex.parent.row)
//logic needed to not reselect last item when collpasing or expanding tree
if (lastCurrentRow === treeView.currentIndex.row && lastParentRow === treeView.currentIndex.parent.row)
{
currentRow = -1
parentRow = -1
}
else
{
lastCurrentRow = treeView.currentIndex.row
lastParentRow = treeView.currentIndex.parent.row
currentRow = treeView.currentIndex.row
parentRow = treeView.currentIndex.parent.row
}
if (currentRow === -1 && parentRow === -1)
{
//nothing selected - do nothing
}
else
{
//omitted some additional logic
}
}
}

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(")", "")

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.

Foreach delegate in QML view

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;
}
}
}
}
// ...
}

Incrementing property variable in a loop

Not sure how to increment "something" in a Repeater loop in QML: variables, properties.
I would prefer to not to have to involve a wrapper to a cpp function.
property int bs: 0
Repeater {
model: 12
bs: {
var vbs = bs + 1; //this works
vbs;
}//ERROR: Cannot assign to non-existent property bs
}
Repeater{
model: 7
Row {
spacing: table.rowSpacing
DiagValue{
value: {trans("sw " + (index + 1))}
width: 60
}
Repeater{
model: 12
CheckBox {
id:myCheckbox
width: 50
height: 50
backingVisible: false
checkable: true
onClicked:{
matrix[i_index? ][j_index? ] = myCheckbox.checked //how to do this assignement??
//pass the matrix to a cpp wrapper.
}
OR
onClicked:{
matrix[i] = myCheckbox.checked //how to do this assignement??
i++;//??
//pass the matrix to a cpp wrapper.
}
}
}
}
You are trying to use the index property of both repeater at the same time. I would do something like this:
Column {
Repeater{
model: 7
Row {
id: currentRow
// Store the index of the current row based on the
// index in the first Repeater
property int rowIndex: index
spacing: 10
Repeater{
id: repeaterColumns
model: 5
CheckBox {
id:myCheckbox
width: 50
height: 50
// currentRow.rowIndex is the line index
// index is the column index
text: "Line " + currentRow.rowIndex + " col " + index
}
}
}
}
}
I don't really get where matrix comes from and if it is a 2D array or 1D array element. One of these should work:
onClicked:{
matrix[currentRow.rowIndex][index] = myCheckbox.checked
}
or
onClicked:{
matrix[currentRow.rowIndex * repeaterColumns.count + index] = myCheckbox.checked
}
The idea here is not to try and increment anything yourself but rely on the proper index properties of your element.

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