How to keep the top-right position of QML item when its size is changing? - qt

I have a toolbar that can be moved (by drag). Depending on the context the content of this toolbar will change, and its size will change accordingly.
My problem is, when the size is changing, the top-left position remains the same and the right border is moving (default and normal behaviour). But I want the top-right position to remain the same and the left border to move instead.
From screen 1 to 2 the toolbar gets smaller, and is shown like the blue rectangle. I want it to be placed like the red rectangle.
How can I achieve this ? Without anchoring on the right of the screen, because the toolbar is movable.

The first thing that comes to mind would be to wrap the toolbar in an Item, and anchor the toolbar to the top right of the item.
import QtQuick 2.8
import QtQuick.Controls 2.3
ApplicationWindow {
id: window
width: 800
height: 800
visible: true
Slider {
id: slider
value: 200
to: 400
}
Item {
x: 600
ToolBar {
id: toolBar
anchors.top: parent.top
anchors.right: parent.right
implicitWidth: slider.value
MouseArea {
anchors.fill: parent
drag.target: toolBar.parent
}
}
}
}
The Item doesn't render anything itself, and has a "zero" size so that the ToolBar is anchored correctly.
Edit: thanks to #GrecKo for coming up with the MouseArea idea. :) This allows you to drag the ToolBar.

A simple solution is to readjust the position of the item when the width changes:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
Window {
visible: true
width: 640
height: 480
Slider {
id: slider
value: 200
to: 400
}
Rectangle {
id: block
color: "red"
width: parseInt(slider.value)
height:50
x: 100
y: 50
readonly property int previousWidth: width
onWidthChanged: {
block.x += previousWidth - width
}
MouseArea {
anchors.fill: parent
drag.target: block
}
}
}
Since onWidthChanged is called before the previousWidth property change, you can easily adjust the x position from previous and new width values.
(Edit: improved my example using #Mitch Slider)

You can do that with Behavior and PropertyAction.
This relies on the feature that you can specify the point in a Behavior when its linked property actually change. You can then add some logic before and after this effective change:
import QtQuick 2.8
import QtQuick.Controls 2.3
ApplicationWindow {
id: window
width: 800
height: 800
visible: true
Slider {
id: slider
value: 200
to: 400
}
Rectangle {
id: rect
width: slider.value
y: 40
height: 40
color: "orange"
Behavior on width {
id: behavior
property real right
SequentialAnimation {
ScriptAction { script: behavior.right = rect.x + rect.width } // the width of the rectangle is the old one
PropertyAction { } // the width of the rectangle changes at this point
ScriptAction { script: rect.x = behavior.right - rect.width } // the width of the rectangle is the new one
}
}
MouseArea {
anchors.fill: parent
drag.target: parent
}
}
}

Related

QML: Move the rectangle outside the window

Is it possible to move the rectangle outside the window? The only thing I came up with is to write custom logic that will resize the top window when moving the rectangle outside the window.
Current behavior (imgur .gif):
Current behavior
Desired behavior (imgur .png):
Desired behavior
main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: root
width: 300
height: 500
visible: true
flags: Qt.ToolTip | Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
color: "#00000000"
Rectangle {
id: draggable
color: "blue"
x: 100
y: 100
width: 100
height: 100
MouseArea {
anchors.fill: parent
property real lastMouseX: 0
property real lastMouseY: 0
onPressed: {
lastMouseX = mouseX
lastMouseY = mouseY
}
onMouseXChanged: {
draggable.x += (mouseX - lastMouseX)
}
onMouseYChanged: {
draggable.y += (mouseY - lastMouseY)
}
}
}
Rectangle {
color: "blue"
x: 100
y: 300
width: 100
height: 100
// ...
}
}
Windows can be children of other Windows. The Window behavior is still subject to certain platform-dependent behavior, but on a Desktop environment child Windows should still be able to move outside the parent Window. So simply changing your Rectangle to be a Window will give you the desired effect.
Window {
id: root
Window {
id: draggable
...
}
}

Creating a horizontal Tumbler or circular SwipeView in QML

I'd like to replace a SwipeView with an Tumbler in QML as I prefer the notion, that there is no first and no last Item.
The problem is, I can't find any other way to get this Tumbler tumble horizontally instead of vertically, but to rotate it by -90° and then rotate the Items back by +90°
This is my code so far, and works as expected:
import QtQuick 2.5
import QtQuick.Controls 2.0
import QtQuick.Window 2.2
Window {
id: root
visible: true
width: 640
height: 480
Row {
id: buttons
spacing: 2
Button {
text: '0'
onClicked: tumbl.currentIndex = 0
}
Button {
text: '1'
onClicked: tumbl.currentIndex = 1
}
Button {
text: '2'
onClicked: tumbl.currentIndex = 2
}
Button {
text: '3'
onClicked: tumbl.currentIndex = 3
}
}
Tumbler {
id: tumbl
rotation: -90 // <---- Rotate there
anchors {
top: buttons.bottom
left: buttons.left
right: buttons.right
bottom: parent.bottom
}
model: 4
delegate: Rectangle {
rotation: 90 // <---- Rotate back
color: 'red'
border.width: 15
Text {
anchors.centerIn: parent
text: index
}
}
visibleItemCount: 1
Component.onCompleted: contentItem.interactive = false
}
}
You can see the two lines, in which I do the rotation marked with a comment.
Does anybody know a way to either produce this circular behavior with a SwipeView or to change the tumble-orientation of the tumbler without this rotation trick?

Component pushed on StackView does not bind correctly

The example below illustrates my problem.
I create a small Rectangle at the top left and clicking on it toggles the color between red and green.
Next, I create a StackView and I push a Rectangle to the StackView and bind the color of this second Rectangle to the color of the top-left rectangle
Expected behavior would be that, clicking on the top-left Rectangle would also change the color of the Rectangle on the StackView since the color was binded. Unfortunately, this is not the case.
Note that things work fine when pushing stackRect2 to the stack (see line in comment)
import QtQuick 2.0
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: mainWindow
visible: true
width: 1280
height: 720
Rectangle {
id: rect
width: 100
height: 100
focus: true
color: toggle? "red":"green"
property var toggle:false;
MouseArea {
anchors.fill: parent
onClicked: rect.toggle = !rect.toggle
}
}
StackView {
id: stack
width: 100
height:100
anchors.left: rect.right
anchors.leftMargin: 10
Component.onCompleted: {
stack.push ({item:stackRect, properties: {color:rect.color}})
//stack.push ({item:stackRect2})
}
}
Component {
id:stackRect
Rectangle {}
}
Component {
id:stackRect2
Rectangle {color:rect.color}
}
}
Apparently, this behavior is expected behavior and is in line with Component::createObject().
Using
stack.push (stackRect, {color:Qt.binding(function() { return rect.color})})
works just fine.

QML ListView scrolling does not produce any animation, when delegates have different width

I am creating a ListView with horizontal orientation. The highlight is set to one fixed position of the view so that the list elements scroll through the visible area when I increment/decrement the current Item. Here is my code for the view:
ListView {
anchors.fill: parent
model: ListModel{
ListElement{name:"x"}
ListElement{name:"y"}
ListElement{name:"z"}
}
delegate:
Rectangle {
property int viewW: ListView.view.width
property bool isCurrent: ListView.isCurrentItem
width: ListView.isCurrent? viewW * 0.4 : viewW * 0.3
Text {
anchors.fill: parent
text: name
}
}
orientation: Qt.Horizontal
highlight: Rectangle {color: "transparent"}
preferredHighlightBegin: 0
preferredHighlightEnd: width*0.4
highlightRangeMode: ListView.StrictlyEnforceRange
}
I want the delegate of the current item to have a greater width than all the other elements. However, when the width of all delegates is not identical, the list scrolling animation (e.g. you can see elements moving to next position, instead of just appearing on the new position) does not apply any more.
How can I have a ListView with the current element showing a different width than the rest of the other elements, while still being able to have the scrolling animations?
The currently selected item can be modified by combining the property to modify with the currentIndex/index properties. The former is the property contained in the ListView to indicate the selected item (as you already know). The latter is a property exposed in the delegate to represent the index of the corresponding item in the list. When we have that
ListView.view.currentIndex === index
we are in the currently selected item. Hence, in your delegate, you can write something like this:
width: ListView.view.currentIndex === index ? 60 : 30
Now the selected item will be twice as large as the other items. However the effect is a little bit ugly. I would go for the following one:
scale: ListView.view.currentIndex === index ? 1.5 : 0.5
Here you are saying that "when this item is the selected one it should grow by 50%, otherwise it should shrink by 50%".
The final code for the width could look like this:
import QtQuick 2.3
import QtQuick.Window 2.0
import QtQuick.Controls 1.2
Window {
id: container
width: 300
height: 150
visible: true
ListView {
id: list
anchors.fill: parent
spacing: 5
model: ListModel{
ListElement{name:"a"}
ListElement{name:"b"}
ListElement{name:"c"}
ListElement{name:"d"}
ListElement{name:"e"}
ListElement{name:"f"}
ListElement{name:"g"}
ListElement{name:"h"}
ListElement{name:"i"}
ListElement{name:"j"}
ListElement{name:"k"}
ListElement{name:"l"}
ListElement{name:"x"}
ListElement{name:"y"}
ListElement{name:"z"}
}
delegate:
Rectangle {
width: ListView.view.currentIndex === index ? 60 : 30 // the magnifying/shrinking
color: "steelblue"
height: ListView.view.height
Text {
anchors.centerIn: parent
text: name
font.pixelSize: 20
}
Behavior on width { // added to smooth the resizing
NumberAnimation { duration: 100 }
}
}
orientation: Qt.Horizontal
highlight: Rectangle {color: "transparent"}
preferredHighlightBegin: 0
preferredHighlightEnd: delegate.width
highlightRangeMode: ListView.StrictlyEnforceRange
}
}
And the effect is not that beautiful as said. I would go for the scale property, as follows:
import QtQuick 2.3
import QtQuick.Window 2.0
import QtQuick.Controls 1.2
Window {
id: container
width: 300
height: 150
visible: true
ListView {
id: list
anchors.fill: parent
spacing: 5
model: ListModel{
ListElement{name:"a"}
ListElement{name:"b"}
ListElement{name:"c"}
ListElement{name:"d"}
ListElement{name:"e"}
ListElement{name:"f"}
ListElement{name:"g"}
ListElement{name:"h"}
ListElement{name:"i"}
ListElement{name:"j"}
ListElement{name:"k"}
ListElement{name:"l"}
ListElement{name:"x"}
ListElement{name:"y"}
ListElement{name:"z"}
}
delegate:
Rectangle {
scale: ListView.view.currentIndex === index ? 1.5 : 0.5
color: "transparent"
width: 30
height: ListView.view.height
Text {
anchors.centerIn: parent
text: name
font.pixelSize: 20
}
Behavior on scale { // added to smooth the scaling
NumberAnimation { duration: 100 }
}
}
orientation: Qt.Horizontal
highlight: Rectangle {color: "steelblue"}
preferredHighlightBegin: 0
preferredHighlightEnd: delegate.width
highlightRangeMode: ListView.StrictlyEnforceRange
}
}
Note that the highlight it strictly maintained at the beginning of the list (i.e. at the left side) as required.

Add elements dynamically to SplitView in QML

I am working with QML and I want to add elements to SplitView dynamically eg. onMouseClick, but so far I didn't find the answer.
What I've found out so far is that the SplitView has it's default property set to it's first child's data property. So I guess I should try and add new dynamically created components with the parent set to that child (splitView1.children[0]). Unfortunately that doesn't work either. What is more the number of children of that first child is zero after the component has finished loading (seems like the SplitLayout's Component.onCompleted event calls a function that moves those children somewhere else). Thus the added children do not render (and do not respond to any of the Layout attached properties).
Please see the following code snippet:
import QtQuick 2.1
import QtQuick.Controls 1.0
import QtQuick.Layouts 1.0
ApplicationWindow {
width: 600
height: 400
SplitView {
anchors.fill: parent
Rectangle {
id: column
width: 200
Layout.minimumWidth: 100
Layout.maximumWidth: 300
color: "lightsteelblue"
}
SplitView {
id: splitView1
orientation: Qt.Vertical
Layout.fillWidth: true
Rectangle {
id: row1
height: 200
color: "lightblue"
Layout.minimumHeight: 1
}
// Rectangle { //I want to add Rectangle to splitView1 like this one, but dynamicly eg.onMouseClick
// color: "blue"
// }
}
}
MouseArea {
id: clickArea
anchors.fill: parent
onClicked: {
console.debug("clicked!")
console.debug("len: " + splitView1.__contents.length); // __contents is the SplitView's default property - an alias to the first child's data property
var newObject = Qt.createQmlObject('import QtQuick 2.1; Rectangle {color: "blue"}',
splitView1, "dynamicSnippet1"); //no effect
// var newObject = Qt.createQmlObject('import QtQuick 2.1; import QtQuick.Layouts 1.0; Rectangle {color: "blue"; width: 50; height: 50}',
// splitView1, "dynamicSnippet1"); //rectangle visible, but not in layout(?) - not resizeable
}
}
}
Is there any way I can make the dynamically created components render properly in the SplitView as the statically added ones?
It appears that the API does not provide support for dynamic insertion of new elements. Even if you do get it to work it would be a hack and might break with future releases. You may need to roll your own control to mimic the behavior you want. Ideally it should be backed by some sort of model.
As of QtQuick Controls 1.3, SplitView has an addItem(item) method.
you have to use the Loader for load dinamicaly objects. in onClicked handle you have to declare sourceComponent property to change the source of the Loader, something like this:
ApplicationWindow {
width: 600
height: 400
SplitView {
anchors.fill: parent
Rectangle {
id: column
width: 200
Layout.minimumWidth: 100
Layout.maximumWidth: 300
color: "lightsteelblue"
}
SplitView {
id: splitView1
orientation: Qt.Vertical
Layout.fillWidth: true
Rectangle {
id: row1
height: 200
color: "lightblue"
Layout.minimumHeight: 1
}
Loader {
id:rect
}
}
}
MouseArea {
id: clickArea
anchors.fill: parent
onClicked: {
console.debug("clicked!")
console.debug("len: " + splitView1.__contents.length) // __contents is the SplitView's default property - an alias to the first child's data property
rect.sourceComponent = algo
}
}
Component {
id:algo
Rectangle {
anchors.fill: parent
color: "blue"
}
}
}
I saw the source code of SplitView, it calculate each split region when Component.onCompleted signal. So I think that is a key point. No matter how you do (insert, dynamic create). The region won't be reset after you insert a new region for split.

Resources