Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Obtaining window-local position updates for element in ListView delegate
Forum Updated to NodeBB v4.3 + New Features

Obtaining window-local position updates for element in ListView delegate

Scheduled Pinned Locked Moved Solved QML and Qt Quick
qmlqml c++
5 Posts 2 Posters 179 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M Offline
    M Offline
    maelstrom
    wrote last edited by maelstrom
    #1

    Hi!

    In my project I have a custom type, PreviewImage (subclasses QQuickItem) embedded within the delegate of a ListView. In C++, I need to get the PreviewImage'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.onCompleted onto the ListView itself 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!

    1 Reply Last reply
    0
    • M maelstrom has marked this topic as solved
    • GrecKoG Offline
      GrecKoG Offline
      GrecKo
      Qt Champions 2018
      wrote last edited by GrecKo
      #3

      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;
      }
      
      M 1 Reply Last reply
      1
      • M Offline
        M Offline
        maelstrom
        wrote last edited by
        #2

        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

        1 Reply Last reply
        0
        • M maelstrom has marked this topic as solved
        • GrecKoG Offline
          GrecKoG Offline
          GrecKo
          Qt Champions 2018
          wrote last edited by GrecKo
          #3

          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;
          }
          
          M 1 Reply Last reply
          1
          • GrecKoG GrecKo

            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;
            }
            
            M Offline
            M Offline
            maelstrom
            wrote last edited by maelstrom
            #4

            @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 :)

            1 Reply Last reply
            0
            • M maelstrom has marked this topic as solved
            • M Offline
              M Offline
              maelstrom
              wrote last edited by maelstrom
              #5

              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;
                  });
              }
              
              
              1 Reply Last reply
              0

              • Login

              • Login or register to search.
              • First post
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt Extensions
              • Unsolved