How to dynamically update a ListView in QML - qt

I have a ListView that displays a list of all notifications for a user. Right now since I'm using Component.onCompleted, if the list updates, the new list is not displayed, but the one which existed during instantiation. How could we solve this? Would using a Loader with a separate component instead help?
property int numNotifications: backend_service.num_notifications
property var notifications: []
onNumNotificationsChanged: {
for(var x=0; x<numNotifications; x++) {
var notif = backend_service.notifications.get(x);
notifications.push(notif)
}
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
color: "black"
height: 500
width: 0.95 * parent.width
ListView {
anchors.fill: parent
model: notifModel
delegate: notifDelegate
}
}
ListModel {
id: notifModel
Component.onCompleted: {
for(var i in notifications) {
notifModel.append({"name": notifications[i]})
}
}
}
Component {
id: notifDelegate
Row {
spacing: 10
Text { text: name; color: "white" }
}
}

Component.onCompleted only runs when the object is built and never again. So using that method to add items to the model is useless, instead you should use the function that reports the new data:
onNumNotificationsChanged: {
for(var x=0; x<numNotifications; x++) {
var notif = backend_service.notifications.get(x);
notifModel.append({"name": notif})
}
}

Related

Dynamic component inside a scrollview can't work

If I replace component with Rectangle, then the scrollview works.....
Any idea? How to include dynamic parts in ScrollView?
Thanks in advance
ScrollView {
id:template1
contentHeight: 2000
....
Component {
id: routeComp
CruiseRouteButton {
}
}
function createTab() {
}
Component.onCompleted: {
for(var i = 0; i < mainWnd.config.routes.length; i++) {
....
let t = routeComp.createObject(template1, cfg);
routes[i] = t
}
createTab()
}
This component is used to dynamically set some routes for user to choose

QML update the property of Item inside the ListView

Item {
Component.onComplete: {
for (var i=0;i < 10;i++) {
myModel.append({"myTxt": "SomeThing"+i});
}
}
ListModel {
id: myModel
}
ListView {
id: listView
width: parent.width
height: parent.height
model: myModel
delegate: Rectangle {
property string propItemState: 0;
MyListItem {
id: test
itemText: myText
itemState: propItemState;
}
}
}
}
I want to update the propItemState once the list is displayed.
For that, I have tried the below method but am getting an undefined error.
I am calling this method once the list model is updated.
function updateListItems() {
for (var index=0;index < listView.count;index++) {
console.log("propItemState: "+listView.contentItem.children[index].propItemState);
listView.contentItem.children[index].propItemState = 2;
}
}
You can bind propItemState to something outside of your delegate. Or you can add signal handler (Connections), which listens to your model (for example; or to some another class) and changes state when your conditions met.
Example:
Item {
Component.onComplete: {
for (var i=0;i < 10;i++) {
myModel.append({"myTxt": "SomeThing"+i});
}
internal.state = "1";
}
ListModel {
id: myModel
}
QtObject {
id: internal
property string state: "0"
}
ListView {
id: listView
width: parent.width
height: parent.height
model: myModel
delegate: Rectangle {
property string propItemState: internal.state
MyListItem {
id: test
itemText: myText
itemState: propItemState;
}
}
}
}

Qml: How to set a property to all GridView items

I am trying to make a change to all items of a GridView.
I have tried to iterate through either the model or the grid, I looked at similar examples on the web, but everything I try ends with Cannot read property 'buttonText' of undefined.
It seems to me that the problem is that the interpreter can't figure out that the item from the grid or model is a Button. But I don't know how to cast it.
If I change the log to only display the item, not any property, (see code snippet), it seems that it knows it is an item... see my experiments below.
The only thing I can make work is set a property (or call a signal, or a function) from the delegate. But that only affects one grid item, not all.
How can I set a property on every item of the grid ? Alternatively, how can I send a signal, or call a function, on every item?
My experiments are in function changeEverythingFunction()
file: Button.qml
Item
{
id: itemButton
signal changeEverything
property int buttonIndex
property string buttonText
...
}
file: Model.qml
Item
{
id: modelItem
ListModel
{
id: listModel
}
property int buttonCount: listModel.count
function changeEverythingFunction()
{
// for (var i = 0; i < buttonCount; i++)
// listModel.setProperty(i, buttonText, "abc")
for(var childIndex in gridItems.contentItem.children)
{
console.log(listModel.get(childIndex).buttonText) // Cannot read property 'buttonText' of undefined
console.log(gridItems.contentItem.children[childIndex].buttonText) // Cannot read property 'buttonText' of undefined
console.log(gridItems.contentItem.children[childIndex]["buttonText"]) // undefined (I saw this in a SO example)
var item = gridItems.contentItem.children[childIndex]
console.log(item) // qml: QQuickItem(0xe496370)
}
}
MouseArea
{
....
Rectangle
{
...
GridView
{
id: gridItems
anchors.fill: parent
clip: true
model: listModel
delegate: Item
{
id: buttonDelegate
Button
{
buttonIndex: gridId
buttonText: itemText
onChangeEverything:
{
changeEverythingFunction();
}
}
}
}
}
}
}
Your approach is in the opposite direction: Your approach is to obtain the item of the view and modify it, but the approach that Qt points out is that the view reflects the information of the model and modifies it when necessary.
The following is a simple example where every time you press on the button with "change me" text increasing the number it shows, but if you press the button with "change all" text it will change all the numbers. As it is observed everything is done through the model, not through the view that are only used to display information or receive user interaction.
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
Window {
visible: true
width: 640
height: 480
ListModel{
id: listmodel
}
function changeAll(){
for(var i=0; i< listmodel.count; ++i){
listmodel.setProperty(i, "number", listmodel.get(i).number + 1)
}
}
GridView{
id: grid
anchors.fill: parent
clip: true
model: listmodel
cellHeight: 120
cellWidth: 120
delegate: Item {
width: grid.cellWidth; height: grid.cellHeight
Column {
anchors.fill: parent
Text { text: model.number; anchors.horizontalCenter: parent.horizontalCenter }
Button{text: "change me"; onClicked: model.number +=1}
Button{text: "change all"; onClicked: changeAll()}
}
}
}
Component.onCompleted: {
for(var i=0; i < 10; ++i){
listmodel.append({"number": 0});
}
}
}

How to know "y" property of an Item when it is created in a ListView

I use a ListView and I need to fill my Window with the last Item that was inserted inside it.
In this case, I set the ListView property: contentY with the y value of myItem
My problem is that when you insert a new Item, I don't know how to know the y value of the Item because it is not yet set.
This is what I try:
ScrollView {
id: scroll
ListView {
model: DelegateModel {
id: visualModel
model: myModel //Model is set in the cpp
delegate: Rectangle {
id: rectangleDelegate
Component.onCompleted: {
rectangleDelegate.ListView.view.contentY = rectangleDelegate.y
console.log(rectangleDelegate.y ) //Not set yet (y = 0)
}
Button {
onClicked {
rectangleDelegate.ListView.view.contentY = rectangleDelegate.y
console.log(rectangleDelegate.y ) //Now it is ok (y = rightValue)
}
}
}
}
}
}
How could I proceed ?
Thanks to #derM, here is a simple example that works:
ScrollView {
id: scroll
ListView {
model: DelegateModel {
id: visualModel
model: myModel //Model is set in the cpp
delegate: Rectangle {
id: rectangleDelegate
onYchanged: {
rectangleDelegate.ListView.view.contentY = rectangleDelegate.y
}
}
}
}
}

Qt QML ComboBox override wheel events

Is there any way to override ComboBox MouseArea to ignore wheel event instead of changing current index? ComboBox itself has no option to change wheel focus behaviour. So far I've tried to override onWheel from CB MouseArea with code like this:
ComboBox {
Component.onCompleted: {
for (var i = 0; i < combobox_ctrl.children.length; ++i) {
console.log(combobox_ctrl.children[i])
console.log(combobox_ctrl.children[i].hasOwnProperty('onWheel'))
if (combobox_ctrl.children[i].hasOwnProperty('onWheel')) {
console.log(combobox_ctrl.children[i]['onWheel'])
combobox_ctrl.children[i]['onWheel'] = function() { console.log("CB on wheel!") }
//combobox_ctrl.children[i]onWheel = function() { console.log("CB on wheel!")
//combobox_ctrl.children[i].destroy()
}
}
}
}
But I get
TypeError: Cannot assign to read-only property "wheel"
Did anyone was able to disable wheel events on ComboBox in Qml?
// EDIT
for example in Slider control I was able to remove wheel event handling like this:
Slider {
Component.onCompleted: {
for (var i = 0; i < slider.children.length; ++i) {
console.log(slider.children[i])
if (slider.children[i].hasOwnProperty("onVerticalWheelMoved") && slider.children[i].hasOwnProperty("onHorizontalWheelMoved")) {
console.log("Found wheel area!")
slider.children[i].destroy()
}
}
}
}
But in slider WheelArea is not responsible for handling "click" events.
You can place MouseArea over ComboBox and steel wheel event.
ComboBox {
anchors.centerIn: parent
model: [ "Banana", "Apple", "Coconut" ]
MouseArea {
anchors.fill: parent
onWheel: {
// do nothing
}
onPressed: {
// propogate to ComboBox
mouse.accepted = false;
}
onReleased: {
// propogate to ComboBox
mouse.accepted = false;
}
}
}
It's not currently possible, as ComboBox is not derived from MouseArea, but FocusScope, which has no support for these kinds of events.
A similar problem was mentioned in a suggestion recently:
Disable mouse wheel scroll event on QtQuick.Controls
If you're after a hacky way of doing it, it seems like the only option you have left is to apply a patch to ComboBox.qml that removes the onWheel handler:
diff --git a/src/controls/ComboBox.qml b/src/controls/ComboBox.qml
index 4e29dfe..3413cac 100644
--- a/src/controls/ComboBox.qml
+++ b/src/controls/ComboBox.qml
## -407,13 +407,6 ## Control {
popup.toggleShow()
overridePressed = false
}
- onWheel: {
- if (wheel.angleDelta.y > 0) {
- __selectPrevItem();
- } else if (wheel.angleDelta.y < 0){
- __selectNextItem();
- }
- }
}
Another alternative that doesn't involve modifying Qt code would be to add an intermediate MouseArea above ComboBox's, and then somehow only forward specific events through to ComboBox's MouseArea. Or, create a custom C++ item that does the equivalent. You may have more control that way.
Ok. After hacking around I've managed to come with solution that is acceptable for me but may introduce some regressions in some situations. pressed and hovered properties are no longer usable
import QtQuick.Controls.Private 1.0
ComboBox {
Component.onCompleted: {
for (var i = 0; i < combobox_ctrl.children.length; ++i) {
if (combobox_ctrl.children[i].hasOwnProperty('onWheel') && combobox_ctrl.children[i] !== mouseArea) {
combobox_ctrl.children[i].destroy()
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: {
if (combobox_ctrl.activeFocusOnPress)
forceActiveFocus()
if (!Settings.hasTouchScreen)
combobox_ctrl.__popup.toggleShow()
}
onClicked: {
if (Settings.hasTouchScreen)
combobox_ctrl.__popup.toggleShow()
}
}
}
This way we can mimic mouse area that was originaly inside the ComboBox. Popup is shown as it was (at least I didn't see any regresion in it yet). However two properties are inaccesible right now
I created a separate file called NonScrollingComboBox.qml with the following code following this post: https://stackoverflow.com/a/33080217/969016
Now I can just use NonScrollingComboBox as a component instead of ComboBox on places where I don't want the mouse scroll to change the value
import QtQuick 2.0
import QtQuick.Controls 1.4
ComboBox {
id: combobox_ctrl
Component.onCompleted: {
for (var i = 0; i < combobox_ctrl.children.length; ++i) {
if (combobox_ctrl.children[i].hasOwnProperty('onWheel')
&& combobox_ctrl.children[i] !== mouseArea) {
combobox_ctrl.children[i].destroy()
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: {
if (combobox_ctrl.activeFocusOnPress)
forceActiveFocus()
combobox_ctrl.__popup.toggleShow()
}
onClicked: {
combobox_ctrl.__popup.toggleShow()
}
}
}
usage:
NonScrollingComboBox {
anchors.verticalCenter: parent.verticalCenter
model: ["item one", "item 2"]
}
This seems to apply only to Qt Quick Controls 1 ComboBox. On Qt Quick Controls 2 ComboBox the wheel mouse event is not enabled by default and can be enabled manually by setting to true the property wheelEnabled (documented in the base class Control). Also the combobox won't keep a "focus" on mouse events so you can freely use the wheel on other mouse areas by just entering them.

Resources