Python – PyQt5 Get another widget location?

PyQt5 Get another widget location?… here is a solution to the problem.

PyQt5 Get another widget location?

I’m trying to map one widget pos(A) to another widget (B) so that B can do some work based on where A is. I have WidgetA get WidgetB pos, these widgets can have different parent hierarchies:

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

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

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

And so on

I’m trying to convert B's position to relative position on A by (two widgets sharing the 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 can’t seem to wrap it properly.

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 want to get (0, 0) because that’s what WidgetB sets.

Solution

The position of the widget is relative to

the parent, it is not relative to the position of the screen, so in your example you should get (400, 400).

According to 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 that’s because you’ve already established the position of the parent widget in (0, 0), and if you change it, it will fail.

Considering where you want A to be relative to B, the solution is as follows:

  1. Use the following method to get the position of widget B relative to the screen:

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

It is passed (0, 0

) because mapToGlobal needs to be relative to the position of the widget using the function, in your case the position of WidgetB relative to WidgetB is (0, 0).

  1. Then map the global location of WidgetA:

    b_a = self. WidgetA.mapFromGlobal(gp)
    

So the following example shows my solution in action:

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_())

Related Problems and Solutions