QML How to call method of a model from the delegate - qt

I have a simple QML program which has one ListView. ListView's model and delegate are defined in a separate QML files.
//Main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
//import TheModel.qml
//import TheDelegate.qml
Window {
id: window
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ListView {
anchors.fill: parent
model: theModel
delegate: theDelegate
focus: true
}
Button{
x: 394
y: 257
text: "press me"
onPressed: theModel.append({"color":"black", "cost": 5.95, "name":"Pizza"})
}
TheDelegate{
id: theDelegate
}
TheModel{
id:theModel
}
}
then the model file
//TheModel.qml
import QtQuick 2.0
ListModel{
ListElement {
color:"red";
name: "Bill Smith"
number: "555 3264"
}
ListElement {
color:"blue";
name: "John Brown"
number: "555 8426"
}
ListElement {
color:"green";
name: "Sam Wise"
number: "555 0473"
}
}
and finally the delegate
//TheDelegate.qml
import QtQuick 2.0
Component {
Rectangle{
color: model.color
width: 100
height: 100
MouseArea{
anchors.fill: parent
onPressed: model.append({"color":"black", "cost": 5.95, "name":"Pizza"})
}
}
}
if I click on delegate's MouseArea the onPressed method will need to create one ListItem, but the issue is that I cannot access the model's function from delegate. what is confusing though, that properties are being accessed in delegate through model.
can anyone point out on the right way of doing this, say if I know that the model is a ListModel and it has append method, but delegate doesn't know that, is there a way to cast model to known type then call a method of it?

This can be done by adding a signal to TheDelegate, which you can connect in the scope where theModel is available. I'd like to call this 'contained components', there is probably some fancy term for it ;-)
Please change TheDelegate.qml to (btw, I also changed it to not be a Component, for reusability):
import QtQuick 2.0
Rectangle{
color: model.color
width: 100
height: 100
signal appendRequested(var newItem)
MouseArea{
anchors.fill: parent
onPressed: appendRequested({"color":"black", "cost": 5.95, "name":"Pizza"})
}
}
Then in main.qml you can use it as following:
ListView {
anchors.fill: parent
model: theModel
delegate: theDelegate
focus: true
}
Component {
id: theDelegate
TheDelegate {
onAppendRequested: theModel.append(newItem)
}
}

The model object is exposed to each delegate in a view, and it provides the model data for that particular delegate. It is not the same as the ListView's model property.
#Amfasis' approach is nice, because it will work with any type of model and view.
If you don't mind tying the delegate to ListView, you can use the attached ListView API:
Component {
Rectangle{
id: root
color: model.color
width: 100
height: 100
MouseArea{
anchors.fill: parent
onPressed: root.ListView.view.model.append({"color":"black", "cost": 5.95, "name":"Pizza"})
}
}
}

Related

Receive signal in newly added listview entry

I am trying to execute a function in a newly added entry to my ListView.
After adding an entry via button I want to execute the just added delegate's onNewEntry function. But only the old delegates execute it.
Minimal example:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.0
Window {
id:root
width: 640
height: 480
visible: true
signal newEntry(int new_row)
property var someProperty
ListModel {
id:listmodel
ListElement {
name: "Bill Smith"
}
ListElement {
name: "John Brown"
}
ListElement {
name: "Sam Wise"
}
}
ListView{
id: listView
width:100
height:200
model: listmodel
delegate: ItemDelegate{
id:delegateId
Text{
text:name
}
Connections { //ISSUE!!!: the new delegate doesnt execute this, just the old ones
target: root
function onNewEntry(new_row){
console.debug(index)
console.debug(new_row)
if(new_row==index){ //doesnt get true
listView.currentIndex = index
setProductData()
}
function setProductData(){
root.someProperty=name
}
}
}
}
}
Button {
anchors.top: listView.bottom
id: btnAdd
text:"+"
onClicked:{
listmodel.append({"name":"Joe Black"})
newEntry(listView.count-1) //emit signal newEntry
}
}
}
output:
qml: 0
qml: 3
qml: 1
qml: 3
qml: 2
qml: 3
My already mentioned workaround is using ListView.onAdd in the delegate:
delegate: ItemDelegate{
ListView.onAdd: {
setProductData()
}
My question is: Why does the newly added entry doesnt listen to the newEntry signal. Thank you
It would be easier to just set the currentIndex after inserting the new row:
Button {
id: btnAdd
onClicked: {
sqlTableModel.insertNewEmptyRow()
listView.currentIndex = listView.count - 1
}
}
I found a better, probably more efficient solution:
delegate: ItemDelegate{
ListView.onAdd: {
listView.currentIndex=index
setProductData()
}
only the newly added delegate receives onAdd not all, like in the approach before.

Can't bind on current item in QML view/model

I have a simple task: need to output some information about current item of ListView separately. For example, in outside Label, but I can't succeed.
I figured out that problem related to where model was defined. If it is separate object with id this behavior happened. But when I define it inlined inside the view, all works fine. This behaviour is very weird and looks like a bug.
To make this code work, just switch to commented model definition. Anybody encounter this problem or may be know something?
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import Qt.labs.calendar 1.0
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true
CalendarModel {
id: myModel
from: new Date(2018, 0, 1)
to: new Date(2018, 11, 31)
}
ColumnLayout {
anchors.fill: parent
Label {
id: myLabel
text: "myLabel1"
}
Label {
text: myModel.monthAt(myView.currentIndex)
}
ListView {
id: myView
Layout.fillWidth: true
Layout.fillHeight: true
snapMode: ListView.SnapOneItem
orientation: ListView.Horizontal
highlightRangeMode: ListView.StrictlyEnforceRange
boundsBehavior: Flickable.StopAtBounds
model: myModel
// model: CalendarModel {
// id: myModel
// from: new Date(2018, 0, 1)
// to: new Date(2018, 11, 31)
// }
delegate: Rectangle {
color: "green"
width: myView.width
height: 200
Text {
anchors.centerIn: parent
text: model.month
}
}
onCurrentIndexChanged: {
console.log(currentIndex)
myLabel.text = String(currentIndex)
}
ScrollIndicator.horizontal: ScrollIndicator {}
}
}
}

QML reset dialog with tabview

I was trying to implement a tabbed Dialog in QML with the means to reset it to the intial values.
Since tabs are dynamically instantiated, none of the straight forward methods seem to work. The parent Dialog can not reference the inner Combobox and the Combobox can not reference the outer Dialog. How can this be achieved?
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
Dialog {
id: dlg
title: "Settings"
visible: true
standardButtons: StandardButton.Apply | StandardButton.Reset
property string val: ""
onApply: console.log(val)
onReset: {
// RESET COMBOBOX TO DEFAULT
}
TabView {
id: tabView
anchors.fill: parent
Tab {
title: "ValueTab"
id: tabVal
GridLayout {
id: gridVal
anchors.fill: parent
GroupBox {
title: qsTr("Choose value")
id: gb
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
id: cl
ComboBox {
id: valueChooser
editable: false
model: ListModel {
id: listModel
ListElement { text: "One" }
ListElement { text: "Two" }
ListElement { text: "Three" }
}
Layout.fillWidth: true
onCurrentTextChanged : val = currentText
}
}
}
}
}
}
}
I am quite unsure, if I got your question right as you say, you can not reference the Dialog from within the Combobox. I can not see the reason why.
Assuming the example of yours contains indeed your problem and all you want to do is to reset the values (and you know the original values) once the reset button is pressed, this is how I would solve it.
Using the Connections-type to connect to the Dialog's reset() from within the Combobox
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
Dialog {
id: dlg
title: "Settings"
visible: true
standardButtons: StandardButton.Apply | StandardButton.Reset
property string val: ""
onApply: console.log(val)
onReset: {
// **DONT** RESET COMBOBOX TO DEFAULT **HERE**
}
TabView {
id: tabView
anchors.fill: parent
Tab {
title: "ValueTab"
id: tabVal
GridLayout {
id: gridVal
anchors.fill: parent
GroupBox {
title: qsTr("Choose value")
id: gb
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
id: cl
ComboBox {
id: valueChooser
editable: false
model: ListModel {
id: listModel
ListElement { text: "One" }
ListElement { text: "Two" }
ListElement { text: "Three" }
}
Layout.fillWidth: true
onCurrentTextChanged : val = currentText
/// *** INTERESTING PART HERE! ***
Connections {
target: dlg
onReset: {
// RESET COMBOBOX TO DEFAULT **HERE** INSTEAD
valueChooser.currentIndex = 0
}
}
}
}
}
}
}
}
}

ComboBox disable an item at a particular index

I have a combobox in qml in a as a TableViewColummn and I define it as follows:
import QtQuick 2.3
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
ListModel {
id: comboModel
ListElement {
text: ""
Index: -1
Dims: -1
}
}
TableViewColumn {
id: imageTypeList
role: "ImageType"
title: "Image Type"
width: 100
delegate: Rectangle {
ComboBox {
anchors.verticalCenter: parent.verticalCenter
anchors.margins: 2
model: comboModel
onActivated : {
console.log(comboModel.get(index).Index)
}
}
}
}
My question is that if it is possible to disable a combobox menu item given a index to the item in the ComboBox. So, I would not like to change the underlying model but actually simply disable the item and not allow the user to select it.
Is it possible to disable a ComboBox menu item ... and not allow the user to select it?
Sure, it is possible.
To do it using Quick Controls 2 you need to create ComboBox delegate this way:
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 2.0
Window {
visible: true
width: 640
height: 200
title: qsTr("Let's disable some items in ComboBox")
ComboBox {
id: control
currentIndex: 0
anchors.centerIn: parent
model: [
{ text: "Enabled item.", enabled: true },
{ text: "Supposed to be disabled. Can't click on it.", enabled: false},
{ text: "Last, but enabled item.", enabled: true}
]
width: 500
textRole: "text"
delegate: ItemDelegate {
width: control.width
text: modelData.text
font.weight: control.currentIndex === index ? Font.DemiBold : Font.Normal
highlighted: ListView.isCurrentItem
enabled: modelData.enabled
}
}
}
If you are using Quick Controls 1, you should provide your own implementation of ComboBox component.

How to apply Qt Graphical Effect on Delegate of Repeater in QML

I want to apply the QtGraphicalEffect ColorOverlay to an Image in a Repeater delegate. The problem is that I have to set the id of the Image as the source of the ColorOverlay, but I don't know the id, because it is dynamically created by the Repeater.
import QtQuick 2.4
import QtGraphicalEffects 1.0
Item {
id:mainItem
width: 800
height: 400
property string vorneColor: "red"
ListModel {
id: safeRailModel
ListElement {name: "vorne"; imageSource:"images/saferail/ring_vorne.png";}
ListElement {name: "vorneLinks"; imageSource:"images/saferail/ring_vorne_links.png"; }
}
Component {
id: imageDelegate
Image {
source: imageSource
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
fillMode: Image.PreserveAspectFit
opacity: 1
visible: true
}
}
Repeater {
id: safeRailRepeater
model: safeRailModel
delegate: imageDelegate
}
Component {
id: effectsDelegate
Item{
id:effectsItem
ColorOverlay {
anchors.fill: safeRailRepeater.itemAt(index)// <-- This doesn't work
source: safeRailRepeater.itemAt(index)// <-- This doesn't work
color: vorneColor
}
}
}
Repeater {
id: safeRailEffectsRepeater
model: safeRailModel
delegate: effectsDelegate
}
}
How can I set source and anchors.fill properties?
I searched everywhere, but I've only found something along the lines of safeRailRepeater.itemAt(index) or safeRailRepeater.itemAt(index).item but neither the former nor the latter works.
Side note: the ColorOverlay doesn't need to be in a seperate delegate and Repeater.
It would be great if somebody has a solution for this problem or could point me in the right direction.
Thank you very much!
The problem is that the itemAt() function call returns null because the other Repeater hasn't loaded its items yet. Also, the function call won't ever be reevaluated, because none of its arguments ever change, so you'll always get null.
The design is a bit odd though; I'd suggest moving the ColorOverlay into the same delegate, as you mentioned that it doesn't have to be in a separate Repeater:
import QtQuick 2.0
import QtQuick.Window 2.0
import QtGraphicalEffects 1.0
Window {
id: mainItem
width: 800
height: 400
visible: true
property string vorneColor: "red"
ListModel {
id: safeRailModel
ListElement { name: "vorne"; vorneColor: "salmon"; }
ListElement { name: "vorneLinks"; vorneColor: "steelblue"; }
}
Component {
id: imageDelegateComponent
Rectangle {
id: delegate
color: "grey"
opacity: 1
visible: true
width: 64
height: 64
layer.enabled: true
layer.effect: ColorOverlay {
color: vorneColor
}
}
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
Repeater {
id: safeRailRepeater
model: safeRailModel
delegate: imageDelegateComponent
}
}
}
Using the layer API of Item is a convenient way of specifying graphical effects.
I also changed the Image to a Rectangle, since we don't have access to those images, and put the Repeater within a row, so that you can see all of the delegates.

Resources