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

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"});
}
}

Related

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

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

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: setProperty has no effect

When I try to change a property value of an item contained into a ListModel the following code has no effect:
Main.qml
import QtQuick 2.0
Item {
anchors.fill: parent
ListModel { id: modelCrayon }
Component.onCompleted: {
for (var i = 0; i < 10; i++)
modelCrayon.append( { _tag: i, _source: "resources/crayon-green.png", _selected: false } )
}
Column {
x: -170
spacing: 0
Repeater {
model: modelCrayon
delegate: Crayon {
tag: _tag
source: _source
selected: _selected
onCrayonSelected: {
for (var i = 0; i < modelCrayon.count; i++) {
if (i == tag) continue;
modelCrayon.setProperty(i, "_selected", false);
}
}
}
}
}
}
Crayon.qml
import QtQuick 2.0
Image {
property bool selected
property int tag
signal crayonSelected()
id: crayon
smooth: true
fillMode: Image.PreserveAspectFit
onSelectedChanged: console.debug(tag, selected)
MouseArea {
anchors.fill: parent
onClicked: {
selected = !selected
if (selected) crayonSelected()
}
}
states: State {
name: "selected"; when: selected == true
PropertyChanges { target: crayon; x: 30 }
}
transitions: Transition {
from: ""; to: "selected"
PropertyAnimation { property: "x"; duration: 500; easing.type: Easing.InOutQuad }
}
}
Nothing is shown on console, so the "selected" var is never changed.
I'm sure there's something obvious I'm missing.
By the way, is there a smarter way to use a ListModel as a OptionBox? I mean I want only ONE item at time must have the selected property == true. Or, in other words, keep tracks of the selected index.
This is a working code to achieve what I asked. But it doens't answer why the property was not set.
ListView {
id: list
anchors.verticalCenter: parent.verticalCenter
height: parent.height
x: -150
spacing: 0
orientation: ListView.Vertical
focus: true
model: modelCrayon
delegate: Crayon {
id: delegate
source: _source
selected: ListView.isCurrentItem
MouseArea {
anchors.fill: parent
onClicked: list.currentIndex = index
}
}
}
I have tested your sample code (the Column version), and it works well with Qt 5.4 / Windows 7 64bit.
What is your running environment?

Move items between ListModels

Assume a QML application with two list models, each holding, say 3, items. Graphically the two lists are displayed in two containers. I'd like to implement a drag-and-drop functionality to reorder the items inside their list and to swap two items between different lists.
Reordering in one list poses no problem due to the move method of the ListModel. For swapping items between the two list models, however, I think I have to use the remove and insert or set operations of ListModel. First experiments using set didn't work.
What is the canonical way to achieve this that also works with transitions? (E.g. just swapping the items' roles is no solution, I will have to swap the items themselves.)
You can just use ListModel.get() to fetch an element from one view and ListModel.append() or ListModel.insert() to put it in another one (not exactly with same type)
The simple example:
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.4
Window {
width: 400
height: 400
visible: true
Component.onCompleted: {
fillModel(model1);
fillModel(model2);
}
Component {
id: delegate
Rectangle {
width: parent.width
property int itemIndex: index
property var view: ListView.view
color: (itemIndex === view.currentIndex) ? "orange" : "white"
height: 20
Text { text: name; color: moved ? "red" : "black"; anchors.centerIn: parent }
MouseArea {
anchors.fill: parent
onClicked: {
view.currentIndex = itemIndex;
}
}
}
}
RowLayout {
anchors.fill: parent
ListView {
id: list1
Layout.fillHeight: true
Layout.fillWidth: true
model: ListModel { id: model1 }
delegate: delegate
}
ColumnLayout {
Layout.alignment: Qt.AlignCenter
width: 50
Button { text: ">>>"; onClicked: moveItem(list1,list2) }
Button { text: "<<<"; onClicked: moveItem(list2,list1) }
}
ListView {
id: list2
Layout.fillHeight: true
Layout.fillWidth: true
model: ListModel { id: model2 }
delegate: delegate
add: Transition {
id: list2Transition
enabled: false
property int fromX
property int fromY
ParallelAnimation {
NumberAnimation { properties: "x"; from: list2Transition.fromX; duration: 300; }
NumberAnimation { properties: "y"; from: list2Transition.fromY; duration: 300;easing.type: Easing.OutCirc; }
PropertyAnimation {property: "color"; from: "red"; to: "white"; duration: 500 }
}
}
}
}
function fillModel(model) {
for(var i = 0;i < 15;i ++)
model.append({name: "item" + i, moved: false});
}
function moveItem(listfrom,listto) {
var item = listfrom.model.get(listfrom.currentIndex);
var newPos1 = listfrom.parent.mapFromItem(listfrom,0,listfrom.currentIndex * 20);
var newPos2 = listto.parent.mapFromItem(listto,0,listto.currentIndex * 20);
list2Transition.fromX = newPos1.x - newPos2.x;
list2Transition.fromY = newPos1.y;
list2Transition.enabled = true;
item.moved = true;
listto.model.insert(listto.currentIndex, item);
listfrom.model.remove(listfrom.currentIndex, 1);
}
}
You can also do the same action by using drag & drop functionality.

How to limit the size of drop-down of a ComboBox in QML

I am using a ComboBox in QML and when populated with a lot of data it exceeds my main windows bottom boarder. From googling I have learned that the drop-down list of a ComboBox is put on top of the current application window and therefore it does not respect its boundaries.
Ideally I would want the ComboBox to never exceed the main applications boundary, but I can not find any property in the documentation.
A different approach would be to limit the number of visible items of the drop-down list so that it do not exceed the window limits for a given window geometry. I was not able to find this in the documentation either and I have run out of ideas.
Take a look to the ComboBox source code, the popup is of a Menu type and it doesn't have any property to limit its size. Moreover, the z property of the Menu is infinite, i.e. it's always on top.
If you Find no way but to use the ComboBox of Qt you can create two models one for visual purpose, I will call it visual model, you will show it in your ComboBox and the complete one , it will be the reference model. Items count in your VisualModel wil be equal to some int property maximumComboBoxItemsCount that you declare . you'll need o find a way that onHovered find the index under the mouse in the visualmodel if it's === to maximumComboBoxIemsCount you do visualModel.remove(0) et visualModel.add(referenceModel.get(maximum.. + 1) and you'll need another property minimumComboBoxIemsCount, same logic but for Scroll Up , I dont know if it will work. but it's an idea
I think there is no solution using the built-in component and you should create your own comboBox. You can start from the following code.
ComboBox.qml
import QtQuick 2.0
Item {
id: comboBox
property string initialText
property int maxHeight
property int selectedItem:0
property variant listModel
signal expanded
signal closed
// signal sgnSelectedChoice(var choice)
width: 100
height: 40
ComboBoxButton {
id: comboBoxButton
width: comboBox.width
height: 40
borderColor: "#fff"
radius: 10
margin: 5
borderWidth: 2
text: initialText
textSize: 12
onClicked: {
if (listView.height == 0)
{
listView.height = Math.min(maxHeight, listModel.count*comboBoxButton.height)
comboBox.expanded()
source = "qrc:/Images/iconUp.png"
}
else
{
listView.height = 0
comboBox.closed()
source = "qrc:/Images/iconDown.png"
}
}
}
Component {
id: comboBoxDelegate
Rectangle {
id: delegateRectangle
width: comboBoxButton.width
height: comboBoxButton.height
color: "#00000000"
radius: comboBoxButton.radius
border.width: comboBoxButton.borderWidth
border.color: comboBoxButton.borderColor
Text {
color: index == listView.currentIndex ? "#ffff00" : "#ffffff"
anchors.centerIn: parent
anchors.margins: 3
font.pixelSize: 12
text: value
font.bold: true
}
MouseArea {
anchors.fill: parent
onClicked: {
listView.height = 0
listView.currentIndex = index
comboBox.selectedItem = index
tools.writePersistence(index,5)
comboBoxButton.text = value
comboBox.closed()
}
}
}
}
ListView {
id: listView
anchors.top: comboBoxButton.bottom
anchors.left: comboBoxButton.left
width: parent.width
height: 0
clip: true
model: listModel
delegate: comboBoxDelegate
currentIndex: selectedItem
}
onClosed: comboBoxButton.source = "qrc:/Images/iconDown.png"
Component.onCompleted: {
var cacheChoice = tools.getPersistence(5);
listView.currentIndex = tools.toInt(cacheChoice)
selectedItem = listView.currentIndex
comboBoxButton.text = cacheModel.get(selectedItem).value
}
}
ComboBoxButton.qml
import QtQuick 2.0
Item {
id: container
signal clicked
property string text
property alias source : iconDownUp.source
property string color: "#ffffff"
property int textSize: 12
property string borderColor: "#00000000"
property int borderWidth: 0
property int radius: 0
property int margin: 0
Rectangle {
id: buttonRectangle
anchors.fill: parent
color: "#00000000"
radius: container.radius
border.width: container.borderWidth
border.color: container.borderColor
Image {
id: image
anchors.fill: parent
source: "qrc:/Images/buttonBackground.png"
Image {
id: iconDownUp
source: "qrc:/Images/iconDown.png"
sourceSize.height:20
sourceSize.width: 20
anchors.verticalCenter: parent.verticalCenter
}
}
Text {
id:label
color: container.color
anchors.centerIn: parent
font.pixelSize: 10
text: container.text
font.bold: true
}
MouseArea {
id: mouseArea;
anchors.fill: parent
onClicked: {
container.clicked()
buttonRectangle.state = "pressed"
startTimer.start()
}
}
Timer{
id:startTimer
interval: 200
running: false;
repeat: false
onTriggered: buttonRectangle.state = ""
}
states: State {
name: "pressed"
when: mouseArea.pressed
PropertyChanges { target: image; scale: 0.7 }
PropertyChanges { target: label; scale: 0.7 }
}
transitions: Transition {
NumberAnimation { properties: "scale"; duration: 200; easing.type: Easing.InOutQuad }
}
}
}
I've used it in some software of mine, hence it is possible that It could not work "out of the box". I use it like this:
ComboBox{
id:cacheChoice
initialText: "None"
anchors.top: baseContainer.top
anchors.topMargin: 2
anchors.right: baseContainer.right
maxHeight: 500
listModel: cacheModel
onExpanded: {
cacheChoice.height = 500
}
onClosed: {
cacheChoice.height = 20
}
}
In case you are working with ComboBox from Qt Quick Controls 2, here's the source code for it:
https://github.com/qt/qtquickcontrols2/blob/5.12/src/imports/controls/ComboBox.qml
Based on that, this override of the behavior works to limit the height to something reasonable:
myComboBox.popup.contentItem.implicitHeight = Qt.binding(function () {
return Math.min(250, myComboBox.popup.contentItem.contentHeight);
});
It is possible to access the hidden MenuStyle within the ComboBoxStyle component. There you can use all the things and hidden things you have within a MenuStyle, including its maximum height.
The thing looks roughly like this.
Not pretty but it works well enough.
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.3
import QtQuick.Window 2.2
ComboBox {
id: comboBox
style: ComboBoxStyle {
// drop-down customization here
property Component __dropDownStyle: MenuStyle {
__maxPopupHeight: 400
__menuItemType: "comboboxitem" //not 100% sure if this is needed
}
}
As it came up resonantly in our team, here is a updated version of the idea shown above. The new version restricts the size automatically to the size of your application.
ComboBox {
id: root
style: ComboBoxStyle {
id: comboBoxStyle
// drop-down customization here
property Component __dropDownStyle: MenuStyle {
__maxPopupHeight: Math.max(55, //min value to keep it to a functional size even if it would not look nice
Math.min(400,
//limit the max size so the menu is inside the application bounds
comboBoxStyle.control.Window.height
- mapFromItem(comboBoxStyle.control, 0,0).y
- comboBoxStyle.control.height))
__menuItemType: "comboboxitem" //not 100% sure if this is needed
} //Component __dropDownStyle: MenuStyle
} //style: ComboBoxStyle
} //ComboBox

Resources