QML : How to get current item in Pathview - qt

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.

Related

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()
}

Invalid grouped property access

In this qml code:
Component {
id: userdelegate
PictureBox {
...
icon: model.icon
icon.heigth: 50
}
}
PictureBox comes from the PictureBox.qml system file in this way:
...
Image {
id: icon
...
width: parent.width; height: 150
}
Running qml, I have the error in the title.
I need to use PictureBox.qml, but I can't change it.
How can I override default height value for PictureBox.qml icon?
You can try to bypass QML's scoping rules by traversing Item children until you can find the Image and manipulate it directly. It's possible it could break in the future, but item.toString() gives you something useful:
item.toString() -> "QQuickImage(0x114054350)"
So, you can try something like this (not tested):
function findItemOfType(type, item) {
if (item.toString().indexOf(type) != -1) {
return child;
}
for (var i=0;i < children.length;++i) {
var child = children[i];
var result = findItemOfType(type, child.children);
if (result != null) {
return result;
}
}
return null;
}
Using it like this:
findItemOfType("QQuickImage", pictureBoxId);

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.

Getting around list<> properties being readonly

I have two custom components written in QML
//XOption.qml
Container {
id: xOption
property string title;
property bool active: false;
function makeActive() {active=true}
onActiveChanged {
//alter appearance of option to reflect whether active/not
}
onTouch {
if (touchEvent reflects a tap) {
//notify underlying c++ engine that I was tapped
//engine will notify parent control that I was tapped by setting the flag var
}
}
//label to display title and some other visual components
}
//XParent.qml
Container {
id: XParent;
property list<XOption> options;
property int selectedOption: 0;
property string flag: cppengine.flag;
onCreationCompleted {
for (var k = 0; k < children.length; ++k) {
if (k==selectedOption)
options[k].makeActive()
}
cppengine.declareParentage(options);
}
onFlagChanged {
if (flag indicates that one of my child options was tapped) {
//determine from flag which option was tapped
tappedOption.makeActive()
//make other options inactive
}
}
}
But now I want to use XParent in another QML document and assign any number of different XOptions to it like so:
Container {
XParent {
options: [
XOption {
title: "title1";
},
XOption {
title: "title2";
}
]
}
}
However, when doing so, I get the error:
Invalid property assignment: "options" is a read-only property
Is there any way I could get around this? I've tried making options a string array type variant, that would contain the title of every child option to create, and then adding a ComponentDefinition for XOption to XParent and creating one for every title that was specified, but if I do that I am unable to call XOption's makeActive(), which is absolutely necessary.

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

Resources