Why dynamic objects inside ScrollView won't scroll with the other content? - qt

So in my QML application I a have got a ScrollView with some fixed content.
Then I'd like to add some dynamic content (with Javascript) inside this ScrollView.
The problem is that this newly created objects won't scroll up and down with the other content (the Image).
What am I doing wrong?
CODE:
import QtQuick 2.15
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
Window {
id: window
width: 640
height: 480
visible: true
title: qsTr("Hello World")
signal hideBounds(bool hide)
property var newArrow: []
color: "light grey"
function destroyArrow(number) {
newArrow[number].destroy(500)
}
ScrollView {
id: scroll
width: parent.width
height: parent.height
contentHeight: pdfImage.height
Image {
id: pdfImage
fillMode: Image.PreserveAspectFit
width:parent.width*0.8
x: (parent.width-width)/2
source: "file:///Users/Riccardo/Downloads/file.png"
}
Component.onCompleted: {
var component = Qt.createComponent("CompleteArrow.qml")
for(var i = 0; i < 3; i++) {
newArrow[i] = component.createObject(scroll, {id: "dynamicArrow"+i, arrayNumber: i, quoteNumber: (i + 1), x: scroll.width*0.05, y: i, width: scroll.width*0.1, height: scroll.width*0.1, mirror: true, flip:false})
newArrow[i].destroyMe.connect(destroyArrow)
}
}
}
}

According to the docs:
Items created dynamically need to be explicitly parented to the contentItem.
So your call to createObject needs to change to this:
newArrow[i] = component.createObject(scroll.contentItem, ...)
EDIT:
Actually, the link to the docs I gave was for Flickable. For some reason, I was thinking a ScrollView was a Flickable, but it's not. I still think the answer might work, but I haven't tried it myself.

Related

QML: Separators between list delegates

Has anyone found a good way to add separators between QML list delegates?
There is a very similar question here already but my problem is a bit more complex: How to set custom separator between items of ListView
Most of the time I use something similar as in an answer there:
ListView {
delegate: ItemDelegate {
...
Separator {
anchors { left: parent.left; bottom: parent.bottom; right: parent.right }
visible: index < ListView.View.count
}
}
}
However, depending on the design and backend data, I don't always have a ListView/Repeater at hand and need to add manual items in a ColumnLayout instead or a mix of some items from a repeater and some manual ones:
ColumnLayout {
ItemDelegate {
...
}
Separator {
Layout.fillWidth: true
}
ItemDelegate {
...
}
}
Now, both of those work but it's extremely annoying to always remember and type that separator. After lots of trying I still haven't been able to figure out a component that would take care of it.
The closest I've come to a custom Layout component like this (e.g ItemGroup.qml):
Item {
default property alias content: layout.data
ColumnLayout {
id: layout
}
Repeater {
model: layout.children
delegate: Separator {
parent: layout.children[index]
anchors { left: parent.left; bottom: parent.bottom; right: parent.right }
visible: index < layout.children.length
}
}
}
Now this works fine for manually adding items to such a group, but again it will not work in many corner cases. For instance putting a Repeater into such an ItemGroup will create a separator for the Repeater too (given it inherits Item and thus is included in children) which results in a visual glitch with one seemingly floating separator too much...
Anyone came up with a more clever solution for this?
I'd try this approach:
Make a custom component based on ColumnLayout.
Use default property ... syntax to capture children added to it into a separate list property.
Create a binding for the children property of ColumnLayout that interleaves each real child in your default property list with one of your Separators (using a Component to declare it and createObject() to create each one).
Here's a working example:
Separator.qml
import QtQuick 2.0
import QtQuick.Layouts 1.12
Rectangle {
Layout.fillWidth: true
height: 1
color: "red"
}
SeparatedColumnLayout.qml
import QtQuick 2.15
import QtQuick.Layouts 1.12
ColumnLayout {
id: layout
default property list<Item> actualChildren
property Component separatorComponent: Qt.createComponent("Separator.qml")
children: {
var result = [];
for(var i = 0;i < actualChildren.length;++i) {
result.push(actualChildren[i]);
if (i < actualChildren.length - 1) {
result.push(separatorComponent.createObject(layout));
}
}
return result;
}
}
main.qml:
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.12
ApplicationWindow {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
SeparatedColumnLayout {
anchors.fill: parent
Text {
Layout.alignment: Qt.AlignHCenter
text: "1"
}
Text {
Layout.alignment: Qt.AlignHCenter
text: "2"
}
Text {
Layout.alignment: Qt.AlignHCenter
text: "3"
}
}
}
The result:

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

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
}
}
}

Updating a QML canvas

I am a QML / Javascript noob, and would like some help with that.
I want to insert some points (represented as small black circles) onto a white QML canvas element and then run an algorithm on them (such as finding convex hulls via an external geometric library)
Here is my QML code.
import QtQuick 2.5
import QtQuick.Window 2.2
Window{
id: root
width: 640
height: 480
visible: true
Canvas {
width: 1000
height: 1000
onPaint: {
var context = getContext("2d");
}
MouseArea {
id: mymouse
anchors.fill: parent
property var arrpoints : []
onClicked: {
// Record mouse-position
arrpoints = arrpoints.concat([mouseX, mouseY])
console.log(arrpoints)
}
}
}
}
So far the above code, opens up a window, with a QML canvas on it, and can keep track of the positions on the canvas (via the array arrpoints) where I single-clicked with my mouse, and outputs the array of clicked-points to the console.
But now, everytime the arrpoints changes, how do I 'tell' QML to draw a small black circle at that point immediately?
I would have thought the onPaint part of QML would trigger the rendering of the new state immediately, but it seems that part is only for the initial drawing on the canvas, before the user starts interacting with it.
You have to call the canvas requestPaint() function to force the painting. It is also advisable to save the data of the positions appropriately: {"x": x_value, "y": y_value}
import QtQuick 2.5
import QtQuick.Window 2.2
Window{
id: root
width: 640
height: 480
visible: true
Canvas {
id: canvas
width: 1000
height: 1000
onPaint: {
var context = getContext("2d")
context.strokeStyle = Qt.rgba(0, 0, 0, 1)
context.lineWidth = 1
for(var i=0; i < mymouse.arrpoints.length; i++){
var point = mymouse.arrpoints[i]
context.ellipse(point["x"]-5, point["y"]-5, 10, 10)
}
context.stroke()
}
MouseArea {
id: mymouse
anchors.fill: parent
property var arrpoints : []
onClicked: {
arrpoints.push({"x": mouseX, "y": mouseY})
canvas.requestPaint()
}
}
}
}

How to center dialog on screen in QtQuick Controls 2?

All my dialogs appear on the top left corner of screen instead of the center.
What is the best way to let the dialogs be placed automatically correct?
import QtQuick 2.7
import QtQuick.Controls 2.2
ApplicationWindow {
id: mainWindow
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Component.onCompleted: {
showMessageBox('Hey this actually works!');
}
function showMessageBox(message) {
var component = Qt.createComponent("MessageDialog.qml")
if(component.status == Component.Ready) {
var dialog = component.createObject(mainWindow)
dialog.title = qsTr("Information")
dialog.text = message
dialog.open()
} else
console.error(component.errorString())
}
}
With a very simple MessageDialog.qml:
import QtQuick 2.7
import QtQuick.Controls 2.2
Dialog {
standardButtons: DialogButtonBox.Ok
property alias text : textContainer.text
Text {
id: textContainer
anchors.fill: parent
horizontalAlignment: Qt.AlignLeft
verticalAlignment: Qt.AlignTop
}
}
The documentation hints, that the Dialog is a descendent of Popup which has x/y-coordinates.
I think those would be a good start to position it.
To your avail:
parent.width - which should be the width of your window
width - which should be your Dialogs width
parent.height
height
Calculate the right positions, and you should be fine.
With this you can create a new base class CenteredDialog.qml
Dialog {
x: (parent.width - width) / 2
y: (parent.height - height) / 2
}
and then use CenteredDialog instead of Dialog all the time.
Further, for dynamic instantiation you might declare the Component in the file, and only set the properties upon instantiation using the component.createObject(parentObject, { property1Name : property1Value, property2Name : property2Value ... }) syntax.
You can set x/y position (like derM said), but you have to recalculate every size change of ApplicationWindow!
Here is another solution:
ApplicationWindow {
id: mainWindow
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Component.onCompleted: {
showMessageBox('Hey this actually works!');
}
Item {
anchors.centerIn: parent
width: msgDialog.width
height: msgDialog.height
MessageDialog {
id: msgDialog
title: qsTr("Information")
visible: false
}
}
function showMessageBox(message) {
msgDialog.text = message
msgDialog.visible = true
}
UPDATE: with dynamic instantiation:
Item {
id: dialogParent
anchors.centerIn: parent
}
function showMessageBox(message) {
var component = Qt.createComponent("MessageDialog.qml")
if(component.status === Component.Ready) {
var dialog = component.createObject(dialogParent)
dialog.title = qsTr("Information")
dialog.text = message
dialog.open()
dialogParent.width = dialog.width
dialogParent.height = dialog.height
} else {
console.error(component.errorString())
}
}
For a generic code which works anywhere, including out of Window/ApplicationWindow, you should use Overlay parent :
Dialog {
parent: Overlay.overlay // <------- global Overlay object
readonly property int margin: 16
width: parent ? (parent.width / 2 - margin) : 128
height: content.height + margin
x: parent ? ((parent.width - width) / 2) : 0
y: parent ? ((parent.height - height) / 2) : 0
modal: true
Label {
id: content
...
}
}
As of Qt 5.12 you can use anchors.centerIn attached property.
Dialog {
anchors.centerIn: parent
// ...
}
Then it will be centered on its parent. If you want it to be centered on its window, just set its parent to Overlay.overlay.
anchors.centerIn: Overlay.overlay

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