pyqt5pyside2qwidgetdimensionsqlabel

PyQt5: How to Get Dimensions of Displayed Widgets


I have PyQt5 QLabels that expand/contract as the QMainWindow size changes. I want to get the dimensions of the QLabels when the QMainWindow is sized to anything other that its initial dimensions.

The script below creates two QLabels in a QMainWindow. The QLabels expand but the top QLabel has a fixed height. The QMainWindow is created with dimensions 400x300 and and displays as screen maximized using showMaximized() (in my case, 1920 x 1080). The script prints the dimensions of the QLabels before and after the QMainWindow displays. Prior to display, width() and height() return default QLabel values and after display (screen maximized) width() and height() return values as if the QMainWindow has physical dimensions of 400x300. Here is wat is printed:

label_1 Size Before Expanding:  100 100
label_2 Size Before Expanding:  100 30
label_1 Size After Expanding:  378 100
label_2 Size After Expanding:  378 171

How can I get the true dimensions for the QLabels when the QMainWindow is maximized? I'm running in a Windows environment.

from PyQt5.QtWidgets import QApplication, QMainWindow, QSizePolicy, QLabel, QVBoxLayout, QWidget
import sys

class MainWindow(QMainWindow):  
    def __init__(self, parent=None):
        super().__init__(parent)
        
        central_widget = QWidget()
        self.setCentralWidget(central_widget)        
        self.setGeometry(100, 100, 400, 300)
        self.layout = QVBoxLayout(central_widget)
        
        self.label_1 = QLabel(self)
        self.label_1.setStyleSheet('background-color: green')
        self.label_1.setFixedHeight(100)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.label_1.setSizePolicy(sizePolicy)
        self.layout.addWidget(self.label_1)
        
        self.label_2 = QLabel(self)
        self.label_2.setStyleSheet('background-color: red')
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.label_2.setSizePolicy(sizePolicy)
        self.layout.addWidget(self.label_2)
        
        print('label_1 Size Before Expanding: ', self.label_1.width(), self.label_1.height())
        print('label_2 Size Before Expanding: ', self.label_2.width(), self.label_2.height())
        self.showMaximized()
        print('label_1 Size After Expanding: ', self.label_1.width(), self.label_1.height())
        print('label_2 Size After Expanding: ', self.label_2.width(), self.label_2.height())
        
if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MainWindow()
    app.exec()

Solution

  • When widgets are created but not yet mapped ("shown") on the screen, they always have a default size:

    The only exception is when size constraints exist, like in your case: the first label has a fixed height, and that's what is shown in the output (remember that "fixed size" means that both minimum and maximum sizes are the same).

    Calling show() or setVisible(True) the first time, automatically creates (amongst others) a Resize event on the top level or parent widget, which automatically activates the layout for that widget and eventually recursively creates Resize events for all widgets managed by its layout, based on the layout computations. This does not happen before showing the widgets the first time.

    This is done for optimization reasons: updating a layout can be very demanding, especially for complex UIs with multiple nested layouts that contain widgets that have different size hints, policies, stretches, etc.

    Since geometries will be updated anyway as soon as the window will be shown, there's no point in setting a size until the full layout has been completed. Also note that using setGeometry() or resize() prior showing the widget the first time will not activate the layout, as explained above.

    That said, it is possible to update sizes based on the current layout even if widgets have not been shown yet: you have to explicitly activate() the layout manager. This is also normally achieved by calling the layout's sizeHint(), after it has been set on its widget.

    But, be aware: in order to get the correct sizes based on the layout, you need to activate all layouts, up to the top level widget. QMainWindow has its own private layout, so you need to activate that too.
    Since you've overwritten the default layout() function with self.layout, the only way to access it is through the super() call.

    Then, there's another problem: functions that change the window state (maximized, minimized, full screen and normal) do not directly resize the window. Those functions (including setWindowState()) actually "ask" the OS to change the window state, then the OS will decide on its own if the request is acceptable and eventually resize the window according to its behavior based on the requested state.
    That resizing will happen at an undefined point after that call, and there's no direct way to know when: the OS might have some fancy animation to show the state change, and that might cause continuous changes in the size or even an abrupt change to the new size after that "process" has finished. Even using processEvents() won't be enough, since that function only processes events directly handled by Qt, and Qt cannot know anything about external OS events.

    The only way to know for sure the size of widgets after any resizing, is by overriding the resizeEvent().

    class MainWindow(QMainWindow):  
        def __init__(self, parent=None):
            # ...
            super().layout().activate()
            self.layout.activate()
            
            print('label_1 Size Before Showing: ', self.label_1.size())
            print('label_2 Size Before Showing: ', self.label_2.size())
            self.showMaximized()
    
        def resizeEvent(self, event):
            super().resizeEvent(event)
            print('label_1 Size After Showing/Resizing: ', self.label_1.size())
            print('label_2 Size After Showing/Resizing: ', self.label_2.size())
    

    This will correctly print, before showMaximized():

    label_1 Size Before Expanding:  PyQt5.QtCore.QSize(388, 100)
    label_2 Size Before Expanding:  PyQt5.QtCore.QSize(388, 182)
    label_1 Size After Resizing:  PyQt5.QtCore.QSize(388, 100)
    label_2 Size After Resizing:  PyQt5.QtCore.QSize(388, 182)
    label_1 Size After Resizing:  PyQt5.QtCore.QSize(1428, 100)
    label_2 Size After Resizing:  PyQt5.QtCore.QSize(1428, 757)
    

    Note that the resizeEvent is called twice: the first one is right after any show*() call, the second is when the window has been actually maximized. If you remove the activate calls above, the first output will be the same as the default values explained at the beginning.