Connect custom Scrollbar and Flickable - qt

I have a virtual large table, and an area that is real drawn. Depending on the position of the content, a certain part of the virtual table is displayed.
And now I'm trying in different ways to synchronize the content and the scrollbar.
In this case, the scrollbar jumps with the content.
AreaScrollBar.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
ScrollBar {
id: root
/// Virtual content size
/// 50000
property real virtualContentSize: 0
/// Real content size
/// 2000
property real contentSize: 0
/// Visible content size
/// 500
property real visibleSize: orientation === Qt.Horizontal ? width : height
/// Position offset step.
/// 100
property real stepAreaPosition: 1
/// The position of the component area [0 .. virtualContentSize - contentSize]
property real areaPosition: 0
/// [0 .. contentSize - visibleSize]
property real positionInArea: 0
property QtObject priv: QtObject {
/// В запасе после перестроения
readonly property real buffer: 0.2 * contentSize - (0.2 * contentSize) % stepAreaPosition
}
size: (orientation === Qt.Horizontal ? width : height) / virtualContentSize
policy: ScrollBar.AlwaysOn
onPositionChanged: {
console.log('onPosChanged', position)
let arP = -1
let pia = -1
if (position < 0) {
position = 0
} else if (position + size > 1) {
position = 1 - size
}
if (position * virtualContentSize + visibleSize
> areaPosition + 0.9 * contentSize) {
pia = priv.buffer
} else if (position * virtualContentSize - areaPosition < 0.1 * contentSize) {
pia = contentSize - (priv.buffer + visibleSize)
} else {
positionInArea = position * virtualContentSize - areaPosition
return
}
arP = position * virtualContentSize - pia
let arp2 = 0
if (arP < 0) {
arp2 = 0
} else if (arP > virtualContentSize - contentSize) {
arp2 = virtualContentSize - contentSize
} else {
arp2 = arP - (arP % stepAreaPosition)
}
positionInArea = position * virtualContentSize - arp2
areaPosition = arp2
}
}
main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Window {
width: 600
height: 600
visible: true
title: qsTr("Hello World")
Column {
focus: true
Keys.onDownPressed: vSb.position += 10 / vSb.virtualContentSize
Keys.onUpPressed: vSb.position -= 10 / vSb.virtualContentSize
Keys.onLeftPressed: hSb.position -= 10 / hSb.virtualContentSize
Keys.onRightPressed: hSb.position += 10 / hSb.virtualContentSize
Row {
Column {
Text { text: qsTr("vSb.position %1").arg(vSb.position) }
Text { text: qsTr("vSb.areaPosition %1").arg(vSb.areaPosition) }
Text { text: qsTr("vSb.positionInArea %1").arg(vSb.positionInArea) }
}
Item {
width: 100
height: 1
}
Column {
Text { text: qsTr("hSb.position %1").arg(hSb.position) }
Text { text: qsTr("hSb.areaPosition %1").arg(hSb.areaPosition) }
Text { text: qsTr("hSb.positionInArea %1").arg(hSb.positionInArea) }
}
}
Rectangle {
id: mainArea
width: 500
height: 500
clip: true
color: "gray"
border.color: "darkgray"
border.width: 3
property int _rows: 50
property int _columns: 10
property int _virtualRows: 500
property int _virtualColumns: 50
property int _rectWidth: 90
property int _rectHeight: 90
property int _spacing: 10
property int _virtualContentWidth: _virtualColumns * (_rectWidth + _spacing)
property int _virtualContentHeight: _virtualRows * (_rectHeight + _spacing)
Flickable {
id: _flick
anchors.fill: parent
contentWidth: mainArea._columns * (mainArea._rectWidth + mainArea._spacing)
contentHeight: mainArea._rows * (mainArea._rectHeight + mainArea._spacing)
Column {
spacing: mainArea._spacing
Repeater {
model: mainArea._rows
Row {
property int rowInd: index
spacing: mainArea._spacing
Repeater {
model: mainArea._columns
Rectangle {
id: rect
property int columnInd: index
width: mainArea._rectWidth
height: mainArea._rectHeight
color: "#3c324a"
Column {
Repeater {
model: rect.height / 10
Rectangle {
width: rect.width
height: 10
color: Qt.lighter(rect.color, 1 + index * 0.1)
}
}
}
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignTop
text: '[%2, %3]'.arg(rowInd).arg(columnInd)
font.pointSize: 7
}
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: ('content\n[%1, %2]'
.arg(vSb.areaPosition / vSb.stepAreaPosition + rowInd)
.arg(hSb.areaPosition / hSb.stepAreaPosition + columnInd))
font.pixelSize: 15
}
}
}
}
}
}
interactive: true
Binding on contentX {
value: {
console.log('_flick.contentX', _flick.contentX, hSb.positionInArea)
return hSb.positionInArea
}
}
Binding on contentY {
value: {
console.log('_flick.contentY', _flick.contentY, vSb.positionInArea)
return vSb.positionInArea
}
}
}
AreaScrollBar {
id: vSb
orientation: Qt.Vertical
stepAreaPosition: mainArea._rectHeight + mainArea._spacing
virtualContentSize: mainArea._virtualRows * stepAreaPosition
contentSize: mainArea._rows * stepAreaPosition
anchors.top: parent.top
anchors.right: parent.right
anchors.bottom: hSb.top
Binding on position {
value: {
console.log('vSb.position', _flick.contentY,
vSb.areaPosition, vSb.virtualContentSize)
return (_flick.contentY + vSb.areaPosition) / vSb.virtualContentSize
}
}
}
AreaScrollBar {
id: hSb
orientation: Qt.Horizontal
stepAreaPosition: mainArea._rectWidth + mainArea._spacing
virtualContentSize: mainArea._virtualColumns * stepAreaPosition
visibleSize: mainArea.width
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: vSb.left
Binding on position {
value: {
console.log('hSb.position', _flick.contentX,
hSb.areaPosition, hSb.virtualContentSize)
return (_flick.contentX + hSb.areaPosition) / hSb.virtualContentSize
}
}
}
}
}
}
Error when I try to drag the canvas

This was always the problem when Qt project added QML. People try to skip learning C++ and do everything in JavaScript.
You need to read the TableView documentation. A TableView is already Flickable and provides a rectangle view into a TableModel. No need to try re-inventing the wheel.
Then look at this for adding scrollbars to TableView.
The correct solution is always create all data in C++, use QML for display only.
As soon as people start trying to implement logic in QML/JavaScript the wheels come off the cart.

Please test this code, I use some parts of your code but I didn't complicate it with the position I just use Flickable and Scrollbar, and the Repeaters that you use for creating your matrix.
I test it and in this way, scrollbars didn't jump.
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5
Window {
id:mainArea
visible: true
width: 640
height: 480
title: qsTr("Flickable and Scrollbar ")
property int _rows: 50
property int _columns: 10
property int _virtualRows: 500
property int _virtualColumns: 50
property int _rectWidth: 90
property int _rectHeight: 90
property int _spacing: 10
property int _virtualContentWidth: _virtualColumns * (_rectWidth + _spacing)
property int _virtualContentHeight: _virtualRows * (_rectHeight + _spacing)
Flickable{
width: parent.width
height: parent.height
contentHeight: mColumnId.implicitHeight
contentWidth: mColumnId.implicitWidth
Column{
id : mColumnId
anchors.fill: parent
spacing: mainArea._spacing
Repeater {
model: mainArea._rows
Row {
property int rowInd: index
spacing: mainArea._spacing
Repeater {
model: mainArea._columns
Rectangle {
id: rect
property int columnInd: index
width: mainArea._rectWidth
height: mainArea._rectHeight
color: "#3c324a"
Column {
Repeater {
model: rect.height / 10
Rectangle {
width: rect.width
height: 10
color: Qt.lighter(rect.color, 1 + index * 0.1)
}
}
}
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignTop
text: '[%2, %3]'.arg(rowInd).arg(columnInd)
font.pointSize: 7
}
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: "Parisa"
font.pixelSize: 15
}
}
}
}
}
}
ScrollBar.vertical: ScrollBar{}
ScrollBar.horizontal: ScrollBar{}
}
}

Related

QML Repeater is not drawn correctly after model is changed

I'm writing a QML application to draw brown blocks inside a blue Rectangle. The application performs this task using a ColumnLayout and a Repeater to draw an arbitrary number of blocks (4 by default):
I'm attempting to change the model of the Repeater dynamically when the user clicks on the screen to force the UI to draw a different amount of blocks. Whenever the desired amount of blocks is changed via blockCount, it triggers the recalculation of blockHeight, the height of each block, so that a smaller amount of blocks can occupy more space on the screen. At least that is the theory!
For debugging purposes, clicking on the screen sets blockCount to 2.
Here's a sample image with the expected result on the Left and the current result on the Right:
As you can see on the image above, when the click happens and rectId.blockCount = 2 is executed, it appears to trigger a sequence of calls that:
Ends up changing the model of the Repeater before blockHeight is recalculated;
Or the anchors for the ColumnLayout are reset, for some bizarre reason;
Or something else is going on;
I'm trying to understand what's causing this behavior and also looking for an approach that allows the application to change the number of blocks dynamically while being able to draw them correctly!
What am I missing?
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.15
Window {
id: wndId
property int wndWidth: 200
property int wndHeight: 300
visible: true
width: wndWidth
height: wndHeight
title: qsTr("Testing ColumnLayout")
Rectangle {
id: rectId
property int borderWidth: 5 // width of the blue frame surrounding the window
property int blockCount: 4 // number of blocks to be drawn using Repeater
property int blocksSpace: 8 // minimum space between the blocks
width: wndId.wndWidth
height: wndId.wndHeight
border.color: "blue"
border.width: borderWidth
// size of each inner rectangle is computed dinamically: changing blockCount should update blockHeight
property int blockWidth: rectId.width - (rectId.borderWidth * 4)
property int blockHeight: updateBlockHeight()
function updateBlockHeight(numBlocks)
{
if (numBlocks === undefined)
numBlocks = rectId.blockCount;
var newHeight = (rectId.height - ((rectId.borderWidth + rectId.blocksSpace)*2) - (rectId.blocksSpace * (numBlocks-1))) / numBlocks;
print("updateBlockHeight: newHeight=", newHeight);
return newHeight;
}
Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)
// draw blocks on top of each other with some space between them
ColumnLayout {
spacing: rectId.blocksSpace
Layout.alignment: Qt.AlignBottom
anchors.bottom: parent.bottom
anchors.bottomMargin: rectId.borderWidth + rectId.blocksSpace
anchors.left: rectId.left
anchors.leftMargin: rectId.borderWidth*2
Repeater {
id: repId
model: rectId.blockCount
// each block size is calculated dinamically
Rectangle {
id: blockId
color: "brown"
width: rectId.blockWidth
height: rectId.blockHeight
// Debug:
Component.onCompleted: {
print("Inner Rectangle")
print(" blockCount=" + rectId.blockCount);
print(" blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
print(" blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
}
Component.onDestruction: print("~Inner Rectangle")
} // inner Rectangle
Component.onCompleted: print("Repeater")
Component.onDestruction: print("~Repeater")
} // Repeater
} // ColumnLayout
MouseArea {
anchors.fill: parent
onClicked: {
print("Mouse clicked!");
// since repId uses blockCount as the model, any change to it should automatically recreate the elements of the Repeater
// here we force blockHeight to be recalculated before the model is changed
rectId.blockHeight = rectId.updateBlockHeight(2)
// and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
rectId.blockCount = 2;
print("blockHeight= " + rectId.blockHeight);
}
}
} // outter Rectangle
} // Window
Why are you calculating the blockHeight and blockWidth while you can leverage the power of ColumnLayout?
Use the Layout.fillWidth and Layout.fillHeight properties to signal the ColumnLayout that the blocks should fill the entire width & height, evenly distributed. And then set the correct size to the ColumnLayout and it will do the calculations you try to program itself.
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.3
Window {
id: wndId
property int wndWidth: 200
property int wndHeight: 300
visible: true
width: wndWidth
height: wndHeight
title: qsTr("Testing ColumnLayout")
Rectangle {
id: rectId
property int borderWidth: 5 // width of the blue frame surrounding the window
property int blockCount: 4 // number of blocks to be drawn using Repeater
property int blocksSpace: 8 // minimum space between the blocks
width: wndId.wndWidth
height: wndId.wndHeight
border.color: "blue"
border.width: borderWidth
Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)
// draw blocks on top of each other with some space between them
ColumnLayout {
spacing: rectId.blocksSpace
Layout.alignment: Qt.AlignBottom
anchors.fill: parent
anchors.margins: rectId.borderWidth * 2
Repeater {
id: repId
model: rectId.blockCount
// each block size is calculated dinamically
Rectangle {
id: blockId
color: "brown"
Layout.fillWidth: true
Layout.fillHeight: true
// Debug:
Component.onCompleted: {
print("Inner Rectangle", index)
print(" blockCount=" + rectId.blockCount);
print(" blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
print(" blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
}
Component.onDestruction: print("~Inner Rectangle")
} // inner Rectangle
Component.onCompleted: print("Repeater")
Component.onDestruction: print("~Repeater")
} // Repeater
} // ColumnLayout
MouseArea {
anchors.fill: parent
onClicked: {
print("Mouse clicked!");
// and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
rectId.blockCount = 2;
print("blockHeight= " + rectId.blockHeight);
}
}
} // outter Rectangle
} // Window
EDIT for keeping blockHeight calculation
If you insist on keeping the calculation since it is more difficult in the real world (fair enough), I would suggest to use implicitHeight and implicitWidth. This works because the Layout engine does not trigger on changes on width/height since it is supposed to set these himself, it does however monitor the implicit sizes:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Layouts 1.3
Window {
id: wndId
property int wndWidth: 200
property int wndHeight: 300
visible: true
width: wndWidth
height: wndHeight
title: qsTr("Testing ColumnLayout")
Rectangle {
id: rectId
property int borderWidth: 5 // width of the blue frame surrounding the window
property int blockCount: 4 // number of blocks to be drawn using Repeater
property int blocksSpace: 8 // minimum space between the blocks
width: wndId.wndWidth
height: wndId.wndHeight
border.color: "blue"
border.width: borderWidth
// size of each inner rectangle is computed dinamically: changing blockCount should update blockHeight
property int blockWidth: rectId.width - (rectId.borderWidth * 4)
property int blockHeight: updateBlockHeight()
function updateBlockHeight(numBlocks)
{
var newHeight = (rectId.height - ((rectId.borderWidth + rectId.blocksSpace)*2) - (rectId.blocksSpace * (rectId.blockCount-1))) / rectId.blockCount;
print("updateBlockHeight: newHeight=", newHeight);
return newHeight;
}
Component.onCompleted: print("Outter Rectangle w=" + rectId.width + " h=" + rectId.height)
// draw blocks on top of each other with some space between them
ColumnLayout {
spacing: rectId.blocksSpace
Layout.alignment: Qt.AlignBottom
anchors.fill: parent
anchors.margins: rectId.borderWidth * 2
Repeater {
id: repId
model: rectId.blockCount
// each block size is calculated dinamically
Rectangle {
id: blockId
color: "brown"
implicitWidth: rectId.blockWidth
implicitHeight: rectId.blockHeight
onXChanged: print("x[",index,"]=", x)
onYChanged: print("y[",index,"]=", y)
// Debug:
Component.onCompleted: {
print("Inner Rectangle", index)
print(" blockCount=" + rectId.blockCount);
print(" blockId.width=" + blockId.width + " blockId.height=" + blockId.height)
print(" blockWidth=" + rectId.blockWidth + " blockHeight=" + rectId.blockHeight)
}
Component.onDestruction: print("~Inner Rectangle")
} // inner Rectangle
Component.onCompleted: print("Repeater")
Component.onDestruction: print("~Repeater")
} // Repeater
} // ColumnLayout
MouseArea {
anchors.fill: parent
onClicked: {
print("Mouse clicked!");
// and finally we change the number of blocks, forcing the Repeater to redraw the model correctly
rectId.blockCount = 2;
print("blockHeight= " + rectId.blockHeight);
}
}
} // outter Rectangle
} // Window
Also, I refactored the updateBlockHeight function, it's not needed to explicitly set it, the QML engine is so smart it will even reevaluate the binding when one of the parameters in the function changes!
(From my comments)
With Qt 5.15.1 on Linux get the expected output. With Qt 5.15 get the output you indicate for what is probably a bug that has been fixed in Qt 5.15.1.

How can I get property in gridview which is Repeater's child

how can i get gridView.itemAtIndex(index).color?
what is i have tried:
contentrepeater.itemAt(5).gridView.model.color;
contentrepeater.itemAt(5).gridView.itemAtIndex(5).color;
But it doesn't work
Rectangle {
anchors.top: bar.bottom
Layout.fillHeight: true
Layout.fillWidth: true
Repeater {
id: contentrepeater
model: 11
Rectangle {
anchors.fill: parent
color: 'red'
visible: false
GridView {
id: gridView
anchors.fill: parent
anchors.topMargin: 10
anchors.leftMargin: 10
cellWidth: 150
cellHeight: 170
clip: true
model: 11
delegate: Rectangle{
height: 160
width: 140
color: '#333333'
}
}
}
}
}
Ultimately you probably don't want to do it that way. It will be hackish and error-prone. For example GridView only provides item access based on position coordinates, not indexes. So you'd need to dive into its children which are going to be created dynamically... it's possible but very messy and not really supported API.
You are better off defining your item models first, then using the GridView (or whatever) to display them. That way you can manipulate objects in the models and changes will be reflected in the view (instead of the other way around like you're trying now).
This example (based on your posted code) creates 4 layouts with 11 squares each and animates the color in each square using a timed script. Note that we need separate instances of the models for each of the GridViews within contentrepeater (otherwise it is only shown in the last view). So the example is a bit more convoluted since the item models are being created dynamically.
I should add that in a "real" application I'd use a different method of tracking the created item models instead of looking them up in the display hierarchy like I have it here. The main point this is trying to demonstrate is to manipulate the displayed items (delegates) via changes to the model data.
import QtQuick 2.9
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import QtQml.Models 2.3
Pane {
id: root
width: 400
height: 650
padding: 9
// rectangle items to create per model
property int itemsPerModel: 11
// prototype object model
property Component itemModel: ObjectModel {}
// prototype model item
property Component delegate: Rectangle {
height: 30
width: 30
color: '#333333'
}
// Creates a new ObjectModel with some Rectangle items as children
function newItemModel() {
var model = itemModel.createObject(root);
for (var i=0; i < itemsPerModel; ++i)
model.append(delegate.createObject(root));
return model;
}
SequentialAnimation {
id: animate
running: true
loops: Animation.Infinite
ScriptAction {
property string nextColor: "blue"
property int nextSet: 0
property int nextItem: 0
script: {
contentrepeater.itemAt(nextSet) // the Rectangle within the GridLayout
.children[0] // the gridView within the Rectangle
.model.get(nextItem) // the model's delegate item (a Rectangle)
.color = nextColor; // set the new color on it.
// advance to next item or set of items.
nextItem = (nextItem+1) % root.itemsPerModel;
if (!nextItem)
nextSet = (nextSet+1) % contentrepeater.count;
nextColor = (nextColor === "blue" ? "orange" : nextColor === "orange" ? "white" : "blue");
}
}
PauseAnimation { duration: 100 }
}
GridLayout {
columns: 2
anchors.fill: parent
Repeater {
id: contentrepeater
model: 4
Rectangle {
color: 'red'
width: 150
height: 170
GridView {
id: gridView
anchors.fill: parent
anchors.topMargin: 10
anchors.leftMargin: 10
cellWidth: 40
cellHeight: 40
clip: true
// here we need a unique instance of the ObjectModel
model: root.newItemModel()
}
}
}
}
}

Virtual Keyboard hides Text QML

I have the following Virtual Keyboard which sets the y of a flickable to a value to not hide text. the settings variable is containts the IntSettingsRow below.
//Administration.qml
K_VirtualKeyBoard{
id: keyboard
z:100
onVisibleChanged: {
if(visible){
if(settings.posY - keyboard.height > 0){
flickable.contentY = settings.posY - keyboard.height + 20
}
else{
flickable.contentY = 0
}
}
if(!visible){
settings.settingsRow.textField.focus = false
flickable.contentY = 0
}
}
}
And i have a IntSettingsRow which is a Textfield to insert Some text.
//GeneralSettings.qml
IntSettingsRow{
id: touchDeactivationTimedTime
height: itemHeight
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 8
validator: IntValidator{bottom: 10; top: 300}
title: qsTr("deactivate time")+":"
value: currentSettings?currentSettings.deactivationTime:0
onValueChanged:{
var val = parseInt(value)
if (!isNaN(val)){
currentSettings?currentSettings.deactivationTime = val:{}
}
}
onEditingFinished: {
if (value === "") textField.text = currentSettings.deactivationTime
}
onFocusChanged: {
settingsRow = touchDeactivationTimedTime
if(!textField.focus){
editingFinished()
posY = 0
}else{
posY = mapToItem(parent, x, y).y
}
}
}
Somehow on the first click into the Textfield the virtual keyboard pops up as expected and the flickable shifts. i hide the keyboard and try it again it doesn't work. So the virtual keyboard pops up but the flickable does not change.
I found a solution with editing the flickable directly from the onfocus in my intsettingsrow. now it works
So i changed my K_VirtualKeyboard to:
K_VirtualKeyBoard{
id: keyboard
z:100
onVisibleChanged: {
if(!visible){
flickable.contentY = 0
}
}
}
And added this to the textfield that triggered the Keyboard
textField.onReleased: {
var keyBoardY
if(keyboard.visible)
keyBoardY = keyboard.y
else
keyBoardY = keyboard.y - keyboard.height
if(keyBoardY < (mapToItem(root.parent.parent,0,0).y + height*2))
flickable.contentY = (mapToItem(root.parent.parent,0,0).y + height*2) - keyBoardY + 8
}

How to translate a QQuickItem's position into its new parent

I have 2 QQuickItems like below which I can fetch on C++ side using the QMLEngine like this.
QQuickItem * quick_item_1 = m_qml_engine->rootObjects()[0]->findChild<QQuickItem*>("quickitem1");
QQuickItem * quick_item_2 = m_qml_engine->rootObjects()[0]->findChild<QQuickItem*>("quickitem2");
Note: quick_item_1's immediate parent is different & quick_item_2's immediate parent is also different. But they both are drawn on the same application window into different immediate parents.
I am drawing both of them offscreen on a different QQuickItem. Let's call it new_parent_surface. I draw both these items on new_parent_surface by changing their parent to new_parent_surface like this.
quick_item_1->setParentItem(new_parent_surface);
quick_item_2->setParentItem(new_parent_surface);
This works fine for the objective of drawing them on a new parent QQuickItem. I get the both quick_item_1 & quick_item_2 drawn on new_parent_surface. Even though new_parent_surface is not drawn on UI, but if I take a snapshot using grabToImage of new_parent_surface, I can see the 2 items drawn on them. Fine till here.
However the positioning of quick_item_1 & quick_item_2 is not correct. I want to position them similar to the way they were positioned their original parent item. I can do some percentage math & try positioning them the same way as they were drawn on their original parent but isn't there a QQQuickItem or Qt API to translate this positioning to a new parent?
I tried to look into QQuickItem's mapping APIs like mapToItem & trying them out like this.
quick_item_2->mapToItem(new_parent_surface, quick_item_2->position());
But the positioning is still not correct.
So, how can I map a QQuickItem's position into its new parent QQuickItem after doing a setParentItem?
Items position is always relative to its parent. And position of the parent is relative to its parent and and so on. But you always can get both relative or global position. QML has lots of coordination translation function. Here is small example that could explain the issue:
import QtQuick 2.7
import QtQuick.Window 2.0
import QtQuick.Controls 2.1
Window {
id:container
width: 800
height: 800
visible: true
Component {
id: rect
Rectangle {
property bool imParent: false
x: 50 + Math.round(Math.random() * 550)
y: 50 + Math.round(Math.random() * 550)
width: 100 + Math.round(Math.random() * 100)
height: 100 + Math.round(Math.random() * 100)
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1);
Drag.active: dragArea.drag.active
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
Text {
anchors.centerIn: parent
text: imParent ? "I'm parent" : "Drag me"
color: "white"
}
}
}
Rectangle {
id: blk
x: 10
y: 10
z: 100
parent: null
height: 50
width: 50
radius: 5
border.color: "white"
color: "black"
}
Repeater {
id: reptr
model: 5
property int pos: 0
Loader {
id: loader
sourceComponent: rect
onLoaded: {
if(blk.parent == null) {
blk.parent = loader.item;
loader.item.imParent = true;
}
}
}
}
Row {
anchors.horizontalCenter: container.contentItem.horizontalCenter
spacing: 2
Button {
text: "Reparent relative to parent"
onClicked: {
reptr.pos ++;
if(reptr.pos >= reptr.model) {
reptr.pos = 0;
}
var item = reptr.itemAt(reptr.pos).item;
blk.parent.imParent = false;
blk.parent = item;
blk.parent.imParent = true;
}
}
Button {
text: "Reparent relative to scene"
onClicked: {
reptr.pos ++;
if(reptr.pos >= reptr.model) {
reptr.pos = 0;
}
var item = reptr.itemAt(reptr.pos).item;
var coord = blk.mapToGlobal(blk.x, blk.y);
blk.parent.imParent = false;
blk.parent = item;
blk.parent.imParent = true;
coord = blk.mapFromGlobal(coord.x, coord.y);
blk.x = coord.x;
blk.y = coord.y;
}
}
}
}

Wrong property values in Component.onCompleted

My problem is the following: I have a custom QML object based on a Rectangle, and I need to execute some javascript function with the width & height of this object when it's created.
To do that, I use Component.onCompleted to execute the javascript but in this function the width and height properties are wrong (like 0;0 or 0;-30), as if they were not yet created.
Code:
import QtQuick 2.3
import "js/kloggr.js" as Game
Rectangle {
property var kloggr: undefined
MouseArea {
anchors.fill: parent
// outputs the right values (about 400;600)
onClicked: console.log(width+";"+height);
}
Component.onCompleted: {
Game.kloggr = this;
kloggr = new Game.Kloggr(width, height);
console.log(width+";"+height); // outputs 0;-30
}
}
This object is created like this:
Kloggr {
id: kloggr
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: pauseBtn.top
}
(I've removed the irrelevant parts of the code)
So what did I do wrong, or what could I do to get the same result?
You have forget to set the width and height of your Rectangle ...
import QtQuick 2.3
import "js/kloggr.js" as Game
Rectangle {
width: 300
height: 300
property var kloggr: undefined
MouseArea {
anchors.fill: parent
// outputs the right values (about 400;600)
onClicked: console.log(width+";"+height);
}
Component.onCompleted: {
Game.kloggr = this;
kloggr = new Game.Kloggr(width, height);
console.log(width+";"+height); // outputs 0;-30
//now h + w = 300
}
}
If your use anchors, change your Rectangle by Item, for exemple
import QtQuick 2.3
import "js/kloggr.js" as Game
Item{
//without W and Height if you don't use the designer, if you use the designer you should set a default size
property var kloggr: undefined
Rectangle{
anchors.fill : parent
MouseArea {
anchors.fill: parent
// outputs the right values (about 400;600)
onClicked: console.log(width+";"+height);
}
Component.onCompleted: {
Game.kloggr = this;
kloggr = new Game.Kloggr(width, height);
console.log(width+";"+height); // outputs 0;-30
}
}//end of rectangle
} //end of item
I did not find out it if was a bug or something but I found a workaround: instead of using Component.onCompleted I used onVisibleChanged:
onVisibleChanged: {
if (visible && kloggr === undefined) {
Game.kloggr = this;
kloggr = new Game.Kloggr(width, height);
}
}
It seems that the width and height properties are only valid when the object is set to visible...
Thank you #yekmen for your help

Resources