PyQt5:如何获取显示的小部件的尺寸

PyQt5: How to Get Dimensions of Displayed Widgets

我有 PyQt5 QLabels expand/contract 随着 QMainWindow 大小的变化。当 QMainWindow 的大小调整为其初始尺寸时,我想获得 QLabel 的尺寸。

下面的脚本在 QMainWindow 中创建了两个 QLabel。 QLabels 扩展但顶部 QLabel 具有固定高度。 QMainWindow 的创建尺寸为 400x300,并使用 showMaximized()(在我的示例中为 1920 x 1080)最大化显示为屏幕。该脚本在 QMainWindow 显示之前和之后打印 QLabel 的尺寸。在显示之前,width()height() return 默认 QLabel 值和显示后(屏幕最大化)width()height() return 值就好像QMainWindow 的物理尺寸为 400x300。这是打印的 wat:

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

当 QMainWindow 最大化时,如何获得 QLabel 的真实尺寸?我 运行 在 Windows 环境中。

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

要获得真实大小,我认为您需要触发 resizeEvent。我不知道在查询大小之前强制事件循环完成最大化的方法。当你在 __init__ 中 运行 时,甚至 app.processEvents() 似乎对此没有影响:

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)
        self.show()
        # ~ 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())
        
    def resizeEvent(self, event):
        print('label_1 Size : ', self.label_1.width(), self.label_1.height())
        print('label_2 Size : ', self.label_2.width(), self.label_2.height())
        
if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MainWindow()
    app.exec()

这给出了

label_1 Size :  100 100
label_2 Size :  100 30
label_1 Size :  1268 100
label_2 Size :  1268 869

创建小部件但尚未在屏幕上映射(“显示”)时,它们始终具有默认大小:

  • 100x30 对于具有父 显式 设置的所有小部件(通过在构造函数中添加父参数,或通过调用 setParent());
  • 640x480 用于所有顶级小部件(没有父 显式 设置的小部件);

唯一的例外是存在尺寸限制,例如您的情况:第一个标签具有固定高度,这就是输出中显示的高度(请记住,“固定尺寸”意味着 两者 最小和最大尺寸相同。

第一次调用 show()setVisible(True),自动在顶层或父窗口小部件上创建(除其他外)Resize 事件,这会自动激活该窗口小部件的布局并最终根据布局计算为其布局管理的所有小部件递归创建 Resize 事件。这确实不会发生在第一次显示小部件之前。

这样做是出于优化原因:更新布局可能要求很高,尤其是对于具有多个嵌套布局的复杂 UI,其中包含具有不同大小提示、策略、拉伸等的小部件。

由于一旦显示 window 几何图形就会更新,因此在完整布局完成之前设置尺寸没有意义。另请注意,使用 setGeometry()resize() prior 第一次显示小部件将 not 激活布局,如解释的以上。

也就是说, 可以根据当前布局更新尺寸,即使小部件尚未显示:您必须显式 activate() 布局管理器.

但是,请注意:为了根据布局获得正确的尺寸,您需要激活 所有 布局,直到顶级小部件。 QMainWindow 有自己的私有布局,所以你也需要激活它。
由于您已经用 self.layout 覆盖了默认的 layout() 函数,因此访问它的唯一方法是通过 super() 调用。

那么,还有一个问题:改变window状态(最大化、最小化、全屏和正常)的函数不会直接调整window的大小].这些函数(包括 setWindowState())实际上“要求”OS 更改 window 状态,然后 OS 将自行决定请求是否可接受并最终调整大小window 根据其基于请求状态的行为。
调整大小将在该调用后的 undefined 点发生,并且没有直接的方法知道何时:OS 可能有一些花哨的动画来显示状态变化,并且在该“过程”完成后,可能会导致尺寸不断变化,甚至突然改变为新尺寸。即使使用 processEvents() 也不够,因为该函数只处理 Qt 直接处理的事件,而 Qt 对外部 OS 事件一无所知。

在任何调整大小 后确定小部件大小的唯一方法是覆盖 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())

这将正确打印,在 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)

请注意 resizeEvent 被调用了两次:第一次是在任何 show*() 调用之后,第二次是在 window 实际最大化时。如果删除上面的 activate 调用,第一个输出将与开头解释的默认值相同。