How to fetch the ListElement associated with the row which is clicked in a TableView in QML? - qt

I have a TableView defined in my QML which will obviously have multiple rows populated by a ListModel.
I want to fetch the ListElement associated with the row which is double clicked.
I have my rowDelegate of the table view defined as such:
rowDelegate: Rectangle {
color: "#D3D3D3"
height: 30
MouseArea {
anchors.fill: parent
onDoubleClicked: {
console.log("table view row clicked...")
// How to fetch the ListElement associated with the row
// and return it for use by another module?
}
}
}
My comment pretty much emphasises what I'm looking for.

You probably don't even need a MouseArea to handle the click in your delegate.
The TableView already has a doubleClicked signal that you can use to retrieve the model data from the clicked row index:
TableView {
model: ListModel {
ListElement {
name: "name 1"
}
ListElement {
name: "name 2"
}
}
TableViewColumn {
title: "name"
delegate: Text {
text: model.name
}
}
rowDelegate: Rectangle {
color: "#D3D3D3"
height: 30
// no MouseArea
}
// handle the click directly in TableView
onDoubleClicked: {
const element = model.get(row)
console.error("doubleClicked on", element.name)
}
}

In scope of your delegate you can use model pseudo-property to fetch associated ListElement (or any other piece of data which is displayed via that delegate). You may think of it as a reference to original data item. It has all properties of ListElement (for example text or color or whatever) and also index property (index of item in your ListModel or any other model).

Related

QML/QtQuick Binding delegate's property with ListView's currentIndex

Inside the delegate, I bind Image's source property to ListView's currentIndex which determines which image to load. This works great:
ListView {
id: answerListView
model: 5
currentIndex: -1
delegate: answerDelegate
}
Component {
id: answerDelegate
Item {
width: 100
height: 100
Image {
source: answerListView.currentIndex === index
? "selected.png" : "not_selected.png"
}
MouseArea {
anchors.fill: parent
onClicked: {
answerListView.currentIndex = index
}
}
Component.onCompleted: {
answerListView.currentIndex = 1; // doesn't work!!
}
}
}
Since currentIndex: -1, it will always show not_selected.png. To show selected.png, I change currentIndex in Component.onLoaded inside delegate.
I was expecting image to load selected.png since currentIndex was updated.
What is the correct way and what am I misunderstanding here?
Ok, new guess:
You want to have the posibility to select multiple Items. As currentIndex only stores one value, which is the value you assigned it last, you can use it to mark only one Item.
Therefore you need to find another way to store your selection. You might for example have a property in the delegate: property bool selected: false which you set to true upon selection.
The problem with this solution is, that it only works if all Items are instantiated at all times. As soon as one Item will be destroyed again, the information will be lost, and uppon the next creation, the selection/unselection is undone.
The better way would be to introduce a role in your model, that stores the selection outside of the non-persistant delegates for you:
ListView {
id: answerListView
model: lm
delegate: answerDelegate
width: 100
height: 220
}
ListModel {
id: lm
ListElement { selected: false }
ListElement { selected: false }
ListElement { selected: false }
ListElement { selected: false }
ListElement { selected: false }
}
Component {
id: answerDelegate
Item {
width: 100
height: 100
Image {
anchors.fill: parent
source: model.selected ? "selected.png" : "notselected.png"
}
Text {
text: (model.selected ? 'selected ' : 'notselected ')
}
Component.onCompleted: {
model.selected = true // doesn't work!!
}
MouseArea {
anchors.fill: parent
onClicked: {
model.selected = !model.selected
}
}
}
}
Another option would probably be a ItemSelectionModel, but I don't know atm, how it works.
Otherwise your example works as expected:
The Item with index 1 is shown, and displays the Image selected.png. All other Items are not shown (for the ListView is to small) but if the would be shown, they would show notselected.png for the answerListView.currentIndex is not equal to their index.

Create QML Items out of DelegateModel

Is it possible to create QML Items out of a DelegateModel?
Here is a example DelegateModel:
DelegateModel
{
id: delegateModel
model: ListModel
{
ListElement { name: "#FAFAFA"; test: "object1" }
ListElement { name: "#000000"; test: "object2" }
}
delegate: Rectangle
{
objectName: test
width: 50
height: 50
color: name
}
Component.onCompleted:
{
Utils.var_dump(items,3)
items.create(0)
Utils.var_dump(items.get(0),3)
}
}
The Result should look like this:
Rectangle
{
objectName: "object1"
width: 50
height: 50
color: "#FAFAFA"
}
Rectangle
{
objectName: "object2"
width: 50
height: 50
color: "#000000"
}
For every ListElement there is a created delegate with the inserted ListElement data.
You can do that with anything that is usable to instantiate a Model (a View)
For example you could use it as a model for a ListView, a GridView or a Repeater. As the model provides the delegate on its own, you do not need to specify any delegate in the View, that instantiates it.
Column {
Repeater {
model: delegateModel
// delegate: ... <--- Nothing here! Uses the delegate from the Model.
}
}
If you use the create(index)-Method, the delegate will be created, but has no parent, so it is not displayed. So you need to set the parent, to have it shown:
Button {
onClicked: {
for (var a = 0; a < dm.items.count; a++) {
var o = dm.items.create(a)
o.parent = r
}
}
}
You need to be aware, that the DelegateModel (without Package and Parts) can't be used in multiple views, as each entry/delegate can be instantiated only once at the same time. If you want to have that,
consider using a QSortFilterProxyModel to filter the stuff, and use as much Views that provide their own delegates, as you want.

Access ListView model within the Repeater component in the delegate

I am using a ListView with a model and a delegate.
The model is a simple ListModel with three items. Each item has a value with the key myFirstRole.
The delegate contains a Repeater component to create an arbitrary number of Labels. The Labels have to use data from the model.
The model of the repeater can not be set to the Listview's model as I have the Repeater using other data.
Here is a minimal example of what I am trying to achieve:
//MyDelegate.qml
Component {
Item {
id: root
width: childrenRect.width
height childrenRect.height
Repeater {
model: 5 //It's not an option to set the repeaters model to the ListViews model. This example just illustrates my problem.
Label {
text: root.ListView.view.model.myFirstRole //This is the line where I want to be able to access the ListView's model, but I can't figure out how to properly reefer to it.
}
}
}
}
//MyListView.qml
ListView {
id: root
delegate: MyDelegate {}
model: ListModel {
ListElement {
myFirstRole: "one"
}
ListElement {
myFirstRole: "two"
}
ListElement {
myFirstRole: "three"
}
}
}
Using Qt 5.7.0 with MSVC2015 32bit
I think that you can't access the roles via the special model property mentioned here (which is what I'm assuming you were trying to do) from the scope of the Repeater. Instead, you can declare a property at the root level of the component that can then be used in nested scopes:
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
ListView {
anchors.fill: parent
model: ListModel {
ListElement { myFirstRole: "Dog" }
ListElement { myFirstRole: "Cat" }
}
delegate: Item {
id: root
width: childrenRect.width
height: childrenRect.height
property string myFirstRoleData: myFirstRole
Repeater {
model: 5
Text {
text: myFirstRoleData
}
}
}
}
}
This might get a bit tedious if you have a lot of properties though. From some quick playing around, it looks like it's also possible to store the entire model object in a property:
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
ListView {
anchors.fill: parent
model: ListModel {
ListElement { myFirstRole: "Dog" }
ListElement { myFirstRole: "Cat" }
}
delegate: Item {
id: root
width: childrenRect.width
height: childrenRect.height
property var modelData: model
Repeater {
model: 5
Text {
text: root.modelData.myFirstRole
}
}
}
}
}
modelData is probably not the best name to use though, seeing as Qt uses that name for models with only one role, but... if you're going with this approach, you're gonna have more than one role anyway. :)
It looks like Qt Quick Controls' (1) Tumbler does this too.
//MyDelegate.qml
Item {
id: root
property var listViewModel // pass the model data to here
width: 100
height: 50
Column {
Repeater {
model: 5 // Use a different model here
Text {
width: 50
height: 10
text: listViewModel.myFirstRole //This is the line where I want to be able to access the ListView's model, but I can't figure out how to properly reefer to it.
}
}
}
}
//MyListView.qml
ListView {
id: root
width: 100
height: 500
delegate: MyDelegate {
listViewModel: model // set the model data here
}
model: ListModel {
ListElement {
myFirstRole: "one"
}
ListElement {
myFirstRole: "two"
}
ListElement {
myFirstRole: "three"
}
}
}
See the comments in the code. It is not trivial to guess what you want to achive, but I hope I guessed right.

How to make QML component (widget) adjusts according to data

I have a Widget as QML component in Qt Quick application which is to be used in various screens to display contents.
How can I use this particular QML component to adjust according to the items in it.
If it's a generic Item you can't: you have to manually set the container's size to fit its content.
The only QML components that fits their content are the Row, Column and Grid elements.
coming in way late, but if you want to have an update-able component you can set the model for the component to any list model like:
Component{
id:comp1
model:model1
}
ListModel {
id: model1
ListElement{
name:"a"
}
ListElement{
name: "b"
}
}
Component {
id: fruitDelegate
Row {
spacing: 10
Text { text: name }
}
}
ListView {
id:listView1
anchors.fill: parent
model: fruitModel
delegate: fruitDelegate
contentWidth: Screen.width
}
then you can update the listview at will
TextInput{
id: text_input1
width:parent.width * 0.80
text:"waddup?"
}
Button {
id: button2
anchors.left: text_input1.right
text: qsTr("Send")
onClicked: {
listView1.model.append( {name: text_input1.text, colorCode:"Red" });
/*text_input1.text = ""*/
}
}

ListElements (ListModel) defined in other qml files?

I got a fonctionnal ListModel defined like this :
ListModel {
id: leftGrid
ListElement { icon: "Images/1.png" }
ListElement { icon: "Images/2.png" }
}
The thing is that I'd like to define ListElement in separate qml files but I really don't know how to do it...
I wrote the qml like this :
//myWidget.qml
import QtQuick 1.0
ListElement {
icon: "Images/X.png"
}
But I don't know how to "invoke" or "instanciate" it in my main file...
I tried :
ListModel {
id: leftGrid
ListElement { icon: "Images/1.png" }
myWidget //qml file
}
and :
ListModel {
id: leftGrid
ListElement { icon: "Images/1.png" }
ListElement { myWidget }
}
Both doesn't work...
Any help with be welcomed, thanks in advance.
I don't think it's possible to have ListElements as separate files. This is because when you do that you are implicitly creating a Component, with your contents inside (in this case the ListElement). However the ListModel can only accept ListElements as its children, not Components with nested ListElements inside.
What you can do however to dynamically define your model items is to declare a ListModel, then add your data via a piece of javascript, for example in your Component.onCompleted handler.
If you look at the API for ListModel you will see it has an append() method, among others.
You can pass a JS dictionary to this method and it will add a new ListElement to the list and populate its properties according to the dictionary.
Example:
import QtQuick 1.0
Rectangle {
width: 360
height: 360
ListView {
id:list
anchors.fill: parent
model: ListModel {
ListElement { foo: "hello" }
}
delegate: Text {
text: foo
width: ListView.view.width
}
Component.onCompleted: {
list.model.append({ "foo": "world" })
}
}
}
Your list will appear with two items in it: "hello" and "world"

Resources