ListView with section, Remove Animation not working for the top item - qt

I'm using a QML ListView with section, click on item to remove with animation. Here the code:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
visible: true
width: 400
height: 400
ListView {
id: list
anchors.fill: parent
clip: true
spacing: 0
onContentYChanged: console.log("onContentYChanged: " + contentY)
onContentHeightChanged: console.log("onContentHeightChanged: " + contentHeight)
model: ListModel {
id: myModel
ListElement {name: "Item 1";type: "A"}
ListElement {name: "Item 2";type: "A"}
ListElement {name: "Item 3";type: "B"}
}
delegate: Rectangle {
width: parent.width
height: 50
color: (index % 2 == 1) ? "#5678a2" : "#88a345"
Text {
anchors.verticalCenter: parent.verticalCenter
text: name
}
MouseArea {
anchors.fill: parent
onClicked: {
console.log("remove: " + index + ", contentY:" + list.contentY)
myModel.remove(index)
}
}
}
section.property: "type"
section.delegate: Rectangle {
height: 30
Text {
anchors.verticalCenter: parent.verticalCenter
text: section
}
}
displaced: Transition {
NumberAnimation { properties: "x,y"; duration: 500; easing.type: Easing.OutCubic }
}
remove: Transition {
NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 500 }
NumberAnimation { property: "scale"; from: 1.0; to: 0; duration: 500 }
}
}
}
When I clicked on the first item(Item 1), it got deleted, but the Item 2 was flying up to outside the window. The ListView displayed the remaining items in wrong positions. ContentY changed to 80 (which was the y position of Item 2 before) instead of remaining at 0.
qml: onContentHeightChanged: 300
qml: onContentHeightChanged: 240
qml: onContentHeightChanged: 210
qml: remove: 0, contentY:0
qml: onContentYChanged: 80
qml: onContentHeightChanged: 160
It will work correctly if:
Delete other items except the top one.
Disable either the section or animation.

I tried your code with Qt 5.13.1. And currently downloading Qt 5.15. For now it looks like it is a bug with section, because I found a lot of not not closed bug reports on bugtracker. I can suggest 2 ways of solving your problem.
Performing animation while locking removal.
Using model with categories.
1st solution I tested by meself. Here is what you need to change to try it:
Delete ListView's removal animations. Add following code to your delegate
ListView.onRemove: SequentialAnimation {
PropertyAction { target: wrapper; property: "ListView.delayRemove"; value: true }
ParallelAnimation {
NumberAnimation { target: wrapper; property: "opacity"; to: 0; duration: 500 }
NumberAnimation { target: wrapper; property: "scale"; to: 0; duration: 500 }
}
PropertyAction { target: wrapper; property: "ListView.delayRemove"; value: false }
}
What is this? ListView has a signal remove() which is called BEFORE removing an item from the view. It is described in documentation It is also noted, that
If a remove transition has been specified, it is applied after this signal is handled, providing that delayRemove is false.
So in delegate you simply block removal from view, do you animation and unblock it. I suppose it won't be as clean and beautiful as you want it to be simply because view doesn't andjust it's size in this case.
2nd solution
I didn't try to implement it, but I can imagine having a model like this:
ListModel {
id: myModel
ListElement { type: "category"; name: "cat1" }
ListElement { name: "delegate1"; type: "delegate"; catrgory: "cat1"}
ListElement { name: "delegate2"; type: "delegate"; catrgory: "cat1"}
ListElement { name: "delegate3"; type: "delegate"; catrgory: "cat1"}
ListElement { type: "category"; name: "cat2" }
ListElement { name: "delegate4"; type: "delegate"; catrgory: "cat2"}
To use this as you want, you will need to castomize your delegate accordingly and removal function accordingly, which will lead to much more complex code in comparison to what it would be if section would work properly.
UPD: Same problem in 5.15

Related

Animation problems with the property when

why doesn't animation in the left work in the first case?
qmlonline.
if you go to the second case and comment out the code with when, then the animation works. the same problem if you disable sequential animation.
this is the first case without animation
this is the second case with animation
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
id: root
property int type: 0
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea {
anchors.horizontalCenter: parent.horizontalCenter
width: 50
height: 50
Rectangle { anchors.fill: parent; color: "blue" }
onClicked: {
root.type = !root.type
// stG.state = root.type ? "right" : "left" // uncomment to the second case
}
}
Rectangle {
id: switcher1
width: 50
height: 50
color: "red"
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
id: switcher2
width: 50
height: 50
color: "green"
anchors.top: switcher1.bottom
anchors.topMargin: 10
}
StateGroup {
id: stG
states: [
State {
name: "left"
when: type === 0 // comment to the second case
PropertyChanges {
target: switcher1
x: 0
}
PropertyChanges {
target: switcher2
x: 0
}
},
State {
name: "right"
when: type === 1 // comment to the second case
PropertyChanges {
target: switcher1
x: root.width - switcher1.width
}
PropertyChanges {
target: switcher2
x: root.width - switcher2.width
}
}
]
transitions: [
Transition {
to: "left"
SequentialAnimation {
NumberAnimation {
target: switcher2
property: "x"
duration: 500
}
NumberAnimation {
target: switcher1
property: "x"
duration: 500
}
}
},
Transition {
to: "right"
SequentialAnimation {
NumberAnimation {
target: switcher1
property: "x"
duration: 500
}
NumberAnimation {
target: switcher2
property: "x"
duration: 500
}
}
}
]
}
}
that's enough
I can give you a way to fix the problem, but it actually feels like there might be a bug with Qt here.
I tried adding a printout to show me what state Qt thinks it's in:
onStateChanged:
{
console.log("state: " + stG.state);
}
The output I got was this:
// First click
> state: right
// Second click
> state:
> state: left
So, what seems to be happening is that for a split second, the state enters some non-existent state and resets your x-values to 0 without using the transitions. Then the correct state gets applied, but we don't see the transition because everything is already at 0. Maybe someone smarter than me can help figure out why we switch to the bad state first.
Thankfully, there is a simple enough fix. If you make the "right" transition reversible, then it works as expected.
Transition {
to: "right"
reversible: true
...
}

QML transition changes immediately, not according to 'duration'

I have the following QML file. I wan't the rectangle myRect to slide in from the right when the root item is clicked (simplified setup). What actually happens is that myRect appears immediately when the root item is clicked.
I checked the running property on the transition and that seems to be fine. It logs true when I click the root item, and then false after 2 seconds.
Does anyone know why the x property doesn't gradually change?
import QtQuick 2.7
Item{
id: root
MouseArea{
anchors.fill: parent
onClicked: {
myRect.state = "visible"
}
}
Rectangle{
id: myRect
width: root.width
height: root.height
state: "hidden"
color: "yellow"
states: [
State {
name: "hidden"
PropertyChanges{
target: myRect
x: myRect.width
}
},
State {
name: "visible"
PropertyChanges{
target: myRect
x: 0
}
}
]
transitions: [
Transition {
NumberAnimation{
duration: 2000
}
onRunningChanged: {
console.log("Running:", running)
}
}
]
}
}
You have to indicate the property, in your case "x"
NumberAnimation{
duration: 2000
properties: "x"
}

QML: How to move an object from a previous set postion using states

I have an object that I want to move from it's previously set position every time that particular state is set. I've tried making a separate property called xPos to get around the binding loop error which is set by the object's new position of x after the state is set, then entering a default state just to be able to switch back to that specific state again since calling the same state does nothing but it doesn't seem to work.
Here is a snippet of my code:
property int xPos: 0
states: [
State {
name: "nextStep"
PropertyChanges {
target: progressBar_Id
x: -1*(progressBar_Id.step.width) + xPos
}
},
State {
name: "previousStep"
PropertyChanges {
target: progressBar_Id
x: progressBar_Id.step.width + xPos
}
},
State {
name: "default"
PropertyChanges {
target: progressBar_Id
x: xPos
}
}
]
transitions: [
Transition {
from: "*"
to: "nextStep"
NumberAnimation {properties: "x"; easing.type: Easing.Linear; duration: 1000}
onRunningChanged: {
if(!running) {
xPos = progressBar_Id.x;
console.info("xPos = " + xPos);
state = "default";
}
}
},
Transition {
from: "*"
to: "previousStep"
NumberAnimation {properties: "x"; easing.type: Easing.Linear; duration: 1000}
onRunningChanged: {
if(!running) {
xPos = progressBar_Id.x;
console.info("xPos = " + xPos);
state = "default";
}
}
}
]
xPos seems to get set the first time from the console output but never applies the new xCoord to the object.
Explicitly specify the id of the item on which you wish to set the state, e.g:
idOfComponent.state = "default"
All QML Items and derivatives have a property called state, so the code needs to be more specific.
Actually came up with a much better alternative using a ListView.
ListView {
id: listView_Id
width: contentWidth
height: bubbleSize
anchors.centerIn: parent
layoutDirection: Qt.RightToLeft
orientation: ListView.Horizontal
spacing: 0
delegate: Item {
width: bubbleSize
height: width
ProgressBubble {
stateText: stateNumber
currentState: state
}
}
model: ListModel { id: progressModel_Id }
}
Another qml file:
progressModel.insert(0, {stateNumber: number++, state: "current"});
But I ran into another problem, is there anyway to change a state within a delegate after it's been added to the ListView?
I've already tried progressModel.set and progressModel.setProperty but doesn't seem to work.
state is a property for qml types, so when you are assigning "state" to "currentState" for "ProgressBubble", its taking the value of state in which " ProgressBubble" is currently present.
Rename "state" to something else like "presentState" and then try.
Moreover id of ListView model(progressModel_Id) and the one used to insert model elements(progressModel) in different file are different, both of them must refer to same id.
After these changes, you can try set property of model. Sample code snippet:
import QtQuick 2.0
Rectangle {
id: root
width: 360
height: 360
property int number: 1
ListView {
id: listView_Id
width: 100 //contentWidth // this creates property binding issue
height: 100 // bubbleSize // I was not sure about this value
anchors.centerIn: parent
layoutDirection: Qt.RightToLeft
orientation: ListView.Horizontal
spacing: 0
delegate: Item {
width: 100 // bubbleSize // I was not sure about this value
height: width
ProgressBubble {
state: "default"
stateText: stateNumber
currentState: presentState
MouseArea {
anchors.fill: parent
onClicked: {
progressModel_Id.set(index,{stateNumber: stateNumber, presentState: "not current"})
}
}
}
}
model: ListModel { id: progressModel_Id }
}
Component.onCompleted:
{
progressModel_Id.insert(0, {stateNumber: number++, presentState: "current"});
}
}

How do I correctly handle mouse events in a QML TableView with overlapping mouse areas?

I've got a delegate attached to my TableViewColumn that contains a MouseArea. I use the MouseArea to detect double clicks on individual cells in the table, which allows me to show a TextField for editing purposes.
The problem is the delegate MouseArea blocks mouse events from propagating through to TableView. This means that the selection behaviour of TableView no longer works. Specifically, I have SelectionMode.ExtendedSelection enabled.
The MouseArea child item is simple and originally looked like this:
MouseArea{
id: mousearea
anchors.fill: parent
onDoubleClicked: {
showTextField()
}
}
After consulting the documentation, it looked like this should work:
MouseArea{
id: mousearea
anchors.fill: parent
propagateComposedEvents: true // new
onDoubleClicked: {
showTextField()
}
onPressed: mouse.accepted = false // new
}
Which it does, except now I cannot pick up double click events anymore (in MouseArea)! Which makes sense, as it states later in the documentation:
pressed(MouseEvent mouse)
When handling this signal, use the accepted property of the mouse parameter to control whether this MouseArea handles the press and all future mouse events until release. The default is to accept the event and not allow other MouseAreas beneath this one to handle the event. If accepted is set to false, no further events will be sent to this MouseArea until the button is next pressed.
There does not seem to be a way to capture mouse events for individual cells at the TableView level. It's my first day playing around with QML, so I might have missed something obvious here, but what are my options? Note I'm using PyQt.
If it is only the the selection you want to achive you can set the selection manually:
TableView {
id: tv
itemDelegate: Item {
Text {
anchors.centerIn: parent
color: styleData.textColor
elide: styleData.elideMode
text: styleData.value
}
MouseArea {
id: ma
anchors.fill: parent
onPressed: {
tv.currentRow = styleData.row
tv.selection.select(styleData.row) // <-- select here.
}
onClicked: {
console.log(styleData.value)
}
}
}
TableViewColumn {
role: 'c1'
title: 'hey'
width: 100
}
TableViewColumn {
role: 'c2'
title: 'tschau'
width: 100
}
model: lm
}
Right now I only select. But you can write your very own selection/deselection-logic.
You might also map from the TableView.__mouseArea to the delegate.
import QtQuick 2.7
import QtQuick.Controls 1.4
ApplicationWindow {
id: appWindow
width: 1024
height: 800
visible: true
ListModel {
id: lm
ListElement { c1: 'hallo1'; c2: 'bye' }
ListElement { c1: 'hallo2'; c2: 'bye' }
ListElement { c1: 'hallo3'; c2: 'bye' }
ListElement { c1: 'hallo4'; c2: 'bye' }
ListElement { c1: 'hallo5'; c2: 'bye' }
ListElement { c1: 'hallo6'; c2: 'bye' }
ListElement { c1: 'hallo7'; c2: 'bye' }
ListElement { c1: 'hallo8'; c2: 'bye' }
ListElement { c1: 'hallo9'; c2: 'bye' }
}
TableView {
id: tv
itemDelegate: Item {
id: mydelegate
signal doubleclicked()
onDoubleclicked: console.log(styleData.value)
Text {
anchors.centerIn: parent
color: styleData.textColor
elide: styleData.elideMode
text: styleData.value
}
Connections {
target: tv.__mouseArea
onDoubleClicked: {
// Map to the clickposition to the delegate
var pos = mydelegate.mapFromItem(tv.__mouseArea, mouse.x, mouse.y)
// Check whether the click was within the delegate
if (mydelegate.contains(pos)) mydelegate.doubleclicked()
}
}
}
TableViewColumn {
role: 'c1'
title: 'hey'
width: 100
}
TableViewColumn {
role: 'c2'
title: 'tschau'
width: 100
}
model: lm
}
}

Animating height

I want to show/hide an element by modifying it's height. Here is an example code showing my issue:
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.4
Window {
id: win
width: 300
height: 300
visible: true
ColumnLayout {
width: parent ? parent.width : 200
Switch {
id: someswitch
Layout.alignment: Qt.AlignCenter
}
Label {
id: myText
text: "dummy"
height: 0
wrapMode: Text.WordWrap
clip: true
Layout.fillWidth: true
Layout.alignment: Qt.AlignCenter
states: State {
name: "visible"
when: someswitch.checked
PropertyChanges { target: myText; height: implicitHeight }
}
Behavior on height {
NumberAnimation { duration: 100 }
}
}
}
}
I didn't add a Transition/Animation yet, but the behavior is already wrong on this stage. someswitch is unchecked by default but the text is shown. On the other hand, after checking the switch, the text hides and never appears back.
How should I handle that? I'd like the text to "slide out". I don't want to change its opacity.
Generally speaking, States consistency should be guaranteed to ensure that Transitions work properly. Consistency can be achieved either:
by defining a consistent default state
by defining all the necessary States, like the other nice answer proposed.
That begin said, it should be noted that the presence of a Layout plays a key role here. The Layout somewhat supersedes the height settings of the Items with its minimumHeight property. In such a scenario States defined over height does not really affect the Label. The obvious solution is to force States consistency but over Layout.preferredHeight, i.e. define the default State as well as the "invisible" with different values for Layout.preferredHeight instead of height. A revisited version of your code could look like this:
Label {
id: myText
text: "dummy"
wrapMode: Text.WordWrap
clip: true
Layout.fillWidth: true
Layout.alignment: Qt.AlignCenter
Layout.preferredHeight: implicitHeight //visible --> layout height = text implicit height
states: State {
name: "invisible"
when: !someswitch.checked
PropertyChanges { target: myText; Layout.preferredHeight: 0 } // invisible --> layout item height forced to zero
}
Behavior on Layout.preferredHeight {
NumberAnimation { duration: 100 }
}
}
A fully working example can be found here (whereas a version with a "visible" state can be found here).
You should use States and Transition in your case. For example:
import QtQuick 2.4
import QtQuick.Window 2.0
import QtQuick.Controls 1.2
Window {
id: mainWindow
width: 600
height: 600
visible: true
CheckBox {
text: "hide/show"
id: someswitch
x: 10
y: 10
}
Rectangle {
id: mytext
width: parent.width
anchors.centerIn: parent
color: "orange"
state: "shown"
states: [
State {
name: "shown"
when: !someswitch.checked
PropertyChanges { target: mytext; height: 300 }
},
State {
name: "hidden"
when: someswitch.checked
PropertyChanges { target: mytext; height: 0 }
}
]
transitions: Transition {
PropertyAnimation { property: "height"; duration: 500; easing.type: Easing.InOutQuad }
}
}
}

Resources