I have a treeview in the qml, the selection mode is ExtendedSelection. Whenever the selected index changes by the user I need to exactly know which tree rows are selected. Here is the TreeView:
Controls.TreeView {
id: myTreeView
Controls.TableViewColumn {
title: "Name"
role: "name"
}
headerVisible: false
anchors.fill: parent
selection: ItemSelectionModel {
onCurrentIndexChanged: {
console.log(myTreeView.selection.selectedRows(0))
console.log(currentIndex)
}
}
selectionMode: Controls.SelectionMode.ExtendedSelection
onDoubleClicked: {
if (isExpanded(index))
collapse(index);
else
expand(index);
}
}
In the example above I just try to print the selected rows to the console to make sure I have the right selection. currentIndex always holds the last selected index(as expected), but the selection.selectedRows() which is supposed to hold all the rows that are currently selected, is always one step behind. For example:
if user (while holding Ctrl) selects rows 1, 2, 3 one by one the selection.selectedRows() will be null, "1", "1,2" and currentIndex will be 1, 2, 3 respectively. Combining these two together, one can get the list of all indexes that are in selected state.
My problem is that if the user releases the Ctrl and selects row 4, then the selection.selectedRows() will be "1,2,3" although it should be null.
To summarize it is not possible to distinguish between the case that the user selects (holding Ctrl) rows 1,2,3,4 and the case in which user first selects rows (holding Ctrl) 1,2,3 and then releases Ctrl and just selects row 4.
I have also tested with myTreeView.selection.selectedIndexes, but still the same behaviour.
It looks like a bug in TreeView to me, please advice how to solve.
Thanks.
You should use onSelectionChanged instead of onCurrentIndexChanged. With that signal you can get a list of selected indexes.
I don't know your whole code, but using the File System Browser example provided by Qt you can check the difference.
In that example, replace
ItemSelectionModel {
id: sel
model: fileSystemModel
}
by
ItemSelectionModel {
id: sel
model: fileSystemModel
onSelectionChanged: {
console.log("onSelectionChanged - selectedRows: " + selectedIndexes)
}
onCurrentIndexChanged: {
console.log("onCurrentIndexChanged - selectedRows: " + selectedIndexes)
}
}
To see how both signals work.
Related
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
In the below code, the property __operatingModus_season updating every 30 seconds. But in frontend, I dont see any value or changes. Please give some heads up if i am wrong.
ListModel{
id: tileListModelBetriebsmodus
Component.onCompleted: {
if(__is_automatik_mode_ON){
tileListModelBetriebsmodus.append({"tileListSource": "../../images/HausScreen/operatingModeAuto.png",
"tileListText": getFrontendText("AUTO"),
"tileListValue": __operatingModus_season
})
}else{
tileListModelBetriebsmodus.append({"tileListSource": "../../images/HausScreen/hamburgerfinger.png",
"tileListText": getFrontendText("MANUAL"),
"tileListValue": __operatingModus_season
})
}
}
}
This way of adding items to the ListModel doesn't make them dynamic, the values are stored on the spot.
You might be able to Create Property Bindings like this:
ListModel{
id: tileListModelBetriebsmodus
Component.onCompleted: {
if(__is_automatik_mode_ON){
tileListModelBetriebsmodus.append({"tileListSource": "../../images/HausScreen/operatingModeAuto.png",
"tileListText": getFrontendText("AUTO"),
"tileListValue": Qt.binding(function() { return __operatingModus_season })
})
}else{
tileListModelBetriebsmodus.append({"tileListSource": "../../images/HausScreen/hamburgerfinger.png",
"tileListText": getFrontendText("MANUAL"),
"tileListValue": Qt.binding(function() { return __operatingModus_season }
})
}
}
}
Note I did not test this, if you would add a minimal working example I could have done so.
Amfasis' answer suggesting the use of Qt.binding() is on the right track, except Qt won't allow assigning a binding object directly to a ListElement. However, it allows assigning plain JS function objects. So you have to do it in two steps instead:
Step 1: Define your dynamically evaluated ListElement role as a JS function (tip: you can also use the arrow syntax to make this more readable), for example:
ListElement {
dynamicLabel: () => config.useLabelA ? labelA : labelB
}
Step 2: Set up a binding inside your delegate between the target item and the model role:
ListView {
delegate: Text {
Component.onCompleted: {
text: Qt.binding(model.dynamicLabel)
}
}
}
Note the need to go through Component.onCompleted as you can't do text: Qt.binding(model.dynamicLabel) directly (QML spits out an error "Invalid use of Qt.binding() in a binding declaration.").
You can't just do text: model.dynamicLabel either as that would only evaluate the JS function once at initial assignment, but not update if e.g. config.useLabelA changed. That's what you need the Qt.binding() wrapper for.
Building on Romain Pokrzywka's answer:
text: valuedUpdated() ? model.dynamicLabel : "" will be evaluated whenever value is updated. valuedUpdated() is a Q_PROPERTY, which returns true. Notify signal can be emitted whenever value is updated.
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.
My code consistently gives me a ReferenceError. I have tried to fix this by using
tv.b1Text = " XD "
as in the example, but it didn't work. How can I fix this error? I believe there's a similar problem here.
Here's my code:
Button {
id: b0
text:"b1's text"
onClicked: {
b1.text = " XD "
}
}
TabView {
id: tv
Tab {
id: tab1
grid {
Button{
id: b1
text:"b1's text"
onClicked: {
//console.log(b1.text)
show_text()
}
}
}
}
}
You have a hierarchy in place here and specific scopes. You cannot access ids without exposing them to higher scopes or without going through the hierarchy, but in the correct way.
Let's examine your TabView: it has a single Tab element which contains a Grid (I'm assuming the grid element is actually a Grid!) which in turns contains the Button you want to modify. If you want to access it from Button b0 you have to:
Select the tab in tv
Select the contained item (via the property of the same name) --> it is Grid
Select the first item of the Grid (which is the only one, our Button)
Hence, in the current setting, a correct code to modify your b1 text from b0 clicked signal handler, is the following:
tv.getTab(0).item.children[0].text = " XD "
Note that data[0] can be used in place of children[0] (see the Item element docs). As you can see here you navigate the hierarchy to reach the QML element to be modified.
As you have seen, the previous code is tedious and error prone. A much better approach would be using the aliasing feature as well as Javascript to improve the overall result; other users already kindly advised you about that.
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
}