Drew
Drew

Reputation: 1261

PyQt5 get pos of another widget?

I am trying to map one widgets pos() (A) to another widget (B), so that B can do some work based on A's position. I have WidgetA that gets WidgetB pos, these widgets can have different parenting hierarchy:

Global
+--- Window
     +--- WidgetA
     +--- WidgetB

Global
+--- Window
     +--- Sub
     |    +--- Sub
     |         +--- WidgetB
     |
     +--- WidgetA

Global
+--- Window
|    +--- WidgetB
|
+--- WidgetA

etc.

I have tried to translate B's position to a relative position on A' via (Both widgets share same parent):

WidgetB.setGeometry(150, 150, 100, 100)

                                                                 # expected vs result
WidgetA.mapFromGlobal(WidgetB.mapToGlobal(WidgetB.pos()))        # 150, 150    125, 200
WidgetA.mapFrom(WidgetA.parent, WidgetB.pos())                   # 150, 150    -25, 50
WidgetA.parent.mapFromGlobal(WidgetB.mapToGlobal(WidgetB.pos())) # 150, 150    300, 300

What is the correct way to do this mapping? I cannot seem to wrap it correctly.

Code:

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5 import QtCore

import sys

class Window(QWidget):

    def __init__(self, Parent=None):
        super().__init__()

        self.WidgetA = QWidget(self)
        self.WidgetA.setAttribute(QtCore.Qt.WA_StyledBackground, True)
        self.WidgetA.setObjectName("WidgetA")

        self.WidgetA.setGeometry(400, 400, 100, 100)

        self.WidgetB = QWidget(self)
        self.WidgetB.setAttribute(QtCore.Qt.WA_StyledBackground, True)
        self.WidgetB.setObjectName("WidgetB")

        self.WidgetB.setGeometry(0, 0, 100, 100)

        self.setGeometry(0, 0, 500, 500)

        self.setStyleSheet("""
        #WidgetA {
            background-color: red;
        }
        #WidgetB {
            background-color: blue;
        }
        """)

    def resizeEvent(self, event):
        print(self.WidgetB.pos())
        print(self.WidgetA.pos())

        print(self.WidgetA.mapFromGlobal(self.WidgetB.mapToGlobal(self.WidgetB.pos())))
        print(self.WidgetA.mapFrom(self, self.WidgetB.pos()))
        print(self.mapFromGlobal(self.WidgetB.mapToGlobal(self.WidgetB.pos())))




if __name__ == "__main__":

    app = QApplication(sys.argv)

    screen = Window()
    screen.show()

    sys.exit(app.exec_())

Output:

PyQt5.QtCore.QPoint()
PyQt5.QtCore.QPoint(400, 400)
PyQt5.QtCore.QPoint(-400, -400)
PyQt5.QtCore.QPoint(-400, -400)
PyQt5.QtCore.QPoint()

I would expect to get (0, 0) since that is what WidgetB is set to.

Upvotes: 4

Views: 10039

Answers (1)

eyllanesc
eyllanesc

Reputation: 244301

The position of a widget is relative to the parent, it is not a position with respect to the screen, so in your example you should obtain (400, 400).

According to the docs:

pos : QPoint

This property holds the position of the widget within its parent widget

If the widget is a window, the position is that of the widget on the desktop, including its frame.

When changing the position, the widget, if visible, receives a move event (moveEvent()) immediately. If the widget is not currently visible, it is guaranteed to receive an event before it is shown.

By default, this property contains a position that refers to the origin.

There are some methods that seem to work but it is because you have established the position of the parent widget in (0, 0), if you change it, it will fail.

Considering that you want the position of A with respect to B, the solution is as follows:

  1. Obtain the position of Widget B with respect to the screen using the following:

    gp = self.WidgetB.mapToGlobal(QtCore.QPoint(0, 0))
    

it is passed (0, 0) because mapToGlobal requires the position with respect to the widget that uses the function, in your case the position of WidgetB with respect to WidgetB is (0, 0).

  1. Then the global position respect the WidgetA is mapped:

    b_a = self.WidgetA.mapFromGlobal(gp)
    

So the following example shows the operation of my solution:

import sys

from random import randint

from PyQt5 import QtCore, QtGui, QtWidgets


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

        self.WidgetA = QtWidgets.QWidget(self)
        self.WidgetA.resize(100, 100)
        self.WidgetA.setAttribute(QtCore.Qt.WA_StyledBackground, True)
        self.WidgetA.setObjectName("WidgetA")

        self.WidgetB = QtWidgets.QWidget(self)
        self.WidgetB.resize(100, 100)
        self.WidgetB.setAttribute(QtCore.Qt.WA_StyledBackground, True)
        self.WidgetB.setObjectName("WidgetB")

        self.posA = QtCore.QPoint(randint(0, self.width()-100), randint(0, self.height()-100))
        self.posB = QtCore.QPoint(randint(0, self.width()), randint(0, self.height()))

        self.WidgetA.move(self.posA)
        self.WidgetB.move(self.posB)

        self.setStyleSheet("""
        #WidgetA {
            background-color: red;
        }
        #WidgetB {
            background-color: blue;
        }
        """)

        self.WidgetA.installEventFilter(self)
        self.WidgetB.installEventFilter(self)
        self.installEventFilter(self)

    def eventFilter(self, obj, event):
        if obj in (self.WidgetA, self.WidgetB, self) and event.type() in (QtCore.QEvent.Resize, QtCore.QEvent.Move) : 

            gp = self.WidgetB.mapToGlobal(QtCore.QPoint(0, 0))
            b_a = self.WidgetA.mapFromGlobal(gp)
            print(b_a, self.posB -self.posA)
            assert(b_a == (self.posB -self.posA))

        return QtWidgets.QWidget.eventFilter(self, obj, event)

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())

Upvotes: 4

Related Questions