Search code examples
pyqtwindowparent-childparent

How to center new window relative to mainwindow in PyQt?


How do I alter my code to show Loading in the center of MainWindow relative to wherever MainWindow is at on the screen?

What it is doing now is placing the Loading animation on the top left corner, ignoring the position of the MainWindow.

Even if I add geomtery to the MainWindow, the result will be the same.

Here is the code, both of the classes has been imported from a py.file:

class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
                    
        self.loading = Loading(parent=self)
        self.ui.check.clicked.connect(self.show_animation)


        self.show()


    def show_animation(self):
        self.loading.show()


class Loading(QWidget):
    def __init__(self, parent=None):
        self.parent = parent
        QWidget.__init__(self)
        self.ui = Ui_loading()
        self.ui.setupUi(self)

        self.setWindowModality(QtCore.Qt.ApplicationModal)
        self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
        
        self.label_animation = QLabel(self)
        self.label_animation.setGeometry(QtCore.QRect(25, 25, 256, 256))
        self.movie = QMovie('giphy1.gif')
        self.label_animation.setMovie(self.movie)
        self.movie.start()

        geo = self.geometry()
        geo.moveCenter(self.parent.geometry().center())
        self.setGeometry(geo)

Solution

  • Your code doesn't work because you're already setting the geometry when you create the Loading instance, and that's too soon: not only when you click the button it's possible that the parent window has been moved or resized, but you're creating the instance when it's not been even shown yet, so it still has a default geometry (usually 640x480 unless it has minimum or maximum size constraints based on its contents).

    In order to center the widget on the parent, you have to consider the geometries when it is going to be shown.

    class Loading(QWidget):
        # ...
        def showEvent(self, event):
            if not event.spontaneous():
                geo = self.geometry()
                geo.moveCenter(self.parent.geometry().center())
                QtCore.QTimer.singleShot(0, lambda: self.setGeometry(geo))
    

    The check on event.spontaneous() is done in order to only move the window when it's explicitly shown (using show() or setVisible(True)), otherwise it would happen in any case, for example when restoring the window after it's been minimized.
    The delayed setGeometry() is required because in certain cases (most commonly, on Linux) some amount of time is required between the request Qt makes to the underlying window system and the moment the window is actually mapped on the screen.