Obtaining window-local position updates for element in ListView delegate
-
Hi!
In my project I have a custom type,
PreviewImage(subclassesQQuickItem) embedded within the delegate of aListView. In C++, I need to get thePreviewImage's position relative to the window to pass off to a winapi function which accepts the position (DwmUpdateThumbnailProperties)The only issue is that when the delegate is first completed, each list item is positioned at (0,0) of the
ListView. See:pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls ApplicationWindow { id: window visible: true ListView { id: view anchors.fill: parent model: ListModel { ListElement {} ListElement {} ListElement {} } delegate: Rectangle { objectName: "listElement" width: 200 height: 100 color: "#0ff000" Component.onCompleted: console.log("(Wrong) position: " + mapToItem(window.contentItem, 0, 0)) Button { width: 20 height: 20 onClicked: console.log("(Correct) position: " + mapToItem(window.contentItem, 0, 0)) } } } }Output:
qml: (Wrong) position: QPointF(0, 0) qml: (Wrong) position: QPointF(0, 0) qml: (Wrong) position: QPointF(0, 0) (after pressing buttons) qml: (Correct) position: QPointF(0, 0) qml: (Correct) position: QPointF(0, 200) qml: (Correct) position: QPointF(0, 400)I have seen another post suggest adding a
Component.onCompletedonto theListViewitself which calls an update function, but I couldn't find the post anymore and it seems quite inconvenient.So, is there a way to detect when the window-global position of an element is updated entirely from within itself?
Thank you!
-
M maelstrom has marked this topic as solved
-
Connecting to xChanged and yChanged of the entire parent hierarchy seems superfluous when property bindings already do that by default anyway.
The following does the trick:readonly property point globalPos: { let pos = Qt.point(x, y); let item = this; while (item = item.parent) pos = Qt.point(pos.x + item.x, pos.y + item.y); return pos; }EDIT: here's an alternative with a for loop instead:
readonly property point globalPos: { let pos = Qt.point(x, y); for (let item = parent; item !== null; item = item.parent) pos = Qt.point(pos.x + item.x, pos.y + item.y); return pos; } -
Alright! I found my answer through some indirect googling:
Looked up how to detect when an element's ancestry moves, got this answer: https://stackoverflow.com/questions/43869586/in-mousearea-onentered-detect-if-the-cause-is-only-that-the-mousearea-moved-a
Which lead me to this answer, which is exactly what I wanted: https://stackoverflow.com/questions/17927714/qml-tracking-global-position-of-a-component
Sigh.. I had thought about this solution before, but I was hoping there was a better one. This one requires ancestor traversal, adding listeners, and updating, and that sounds very inefficient. Oh well. If it works, it works
-
M maelstrom has marked this topic as solved
-
Connecting to xChanged and yChanged of the entire parent hierarchy seems superfluous when property bindings already do that by default anyway.
The following does the trick:readonly property point globalPos: { let pos = Qt.point(x, y); let item = this; while (item = item.parent) pos = Qt.point(pos.x + item.x, pos.y + item.y); return pos; }EDIT: here's an alternative with a for loop instead:
readonly property point globalPos: { let pos = Qt.point(x, y); for (let item = parent; item !== null; item = item.parent) pos = Qt.point(pos.x + item.x, pos.y + item.y); return pos; } -
Connecting to xChanged and yChanged of the entire parent hierarchy seems superfluous when property bindings already do that by default anyway.
The following does the trick:readonly property point globalPos: { let pos = Qt.point(x, y); let item = this; while (item = item.parent) pos = Qt.point(pos.x + item.x, pos.y + item.y); return pos; }EDIT: here's an alternative with a for loop instead:
readonly property point globalPos: { let pos = Qt.point(x, y); for (let item = parent; item !== null; item = item.parent) pos = Qt.point(pos.x + item.x, pos.y + item.y); return pos; }@GrecKo I see! This seems like a very good middle ground. My only concern is with if the element is ever re-parented. I don't think this happens, so it's not really an issue (though maybe QML takes care of this for me already? Ie binding to parents as well?). But I think my element is initially unparented, could be wrong though.
EDIT: Seems like it does! So whenever the parents change, the property binding gets rerun, unbinding all previous connections, and then rebinding to ancestry. Nice work!
Thanks for your answer anyway! I'm sure this will be a useful resource for others to come :)
-
M maelstrom has marked this topic as solved
-
Bonus question: Would you happen to know how to re-implement this in C++? I tried my best but I don't think my best is good enough haha:
#pragma once #include <QQuickItem> #include <qproperty.h> // Item that tracks when its window-local position is changed at any point class PositionTrackingItem : public QQuickItem { Q_OBJECT QML_ELEMENT Q_PROPERTY(QPointF globalPosition READ getGlobalPosition NOTIFY globalPositionChanged BINDABLE bindableGlobalPosition) public: PositionTrackingItem(QQuickItem* parent = nullptr); inline QPointF getGlobalPosition() const { return globalPosition; } QBindable<QPointF> bindableGlobalPosition() { return &globalPosition; } signals: void globalPositionChanged(); private: Q_OBJECT_BINDABLE_PROPERTY(PositionTrackingItem, QPointF, globalPosition, &PositionTrackingItem::globalPositionChanged) };#include "PositionTrackingItem.h" #include <qproperty.h> // https://forum.qt.io/post/833294 PositionTrackingItem::PositionTrackingItem(QQuickItem* parent) : QQuickItem(parent) { globalPosition.setBinding([&]() { QPointF pos = QPointF(property("x").toFloat(), property("y").toFloat()); for (QQuickItem* item = property("parent").value<QQuickItem*>(); item != nullptr; item = item->property("parent").value<QQuickItem*>()) pos += QPointF(item->property("x").toFloat(), item->property("y").toFloat()); return pos; }); }