如何管理 "QMainWindow" 小部件内的框架?

How can I manage the frames inside a "QMainWindow" widget?

这些天我试图复制一个已经用 Tkinter 和 PyQt5 编写的 GUI,但我是这个框架的新手,我仍然不知道如何完成一些步骤。这是我的代码:

from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from PyQt5 import QtCore as qtc
import sys

class MainWindow(qtw.QMainWindow):
    def __init__(self):
        """MainWindow constructor"""
        super().__init__()

        self.title="Main Window"
        self.setWindowTitle(self.title)
        self.top, self.left, self.width, self.height = 250, 250, 760, 620
        self.setGeometry(self.top, self.left, self.width, self.height)
        self.setMinimumSize(760, 620)

        self.CreateMenu()
        self.CreateFrames()

        self.show()

    def CreateMenu(self):
        MainMenu=self.menuBar()
        EditMenu=MainMenu.addMenu("Test1")
        HelpMenu=MainMenu.addMenu("Test2")

        Test1_1=qtw.QAction("Test1-1", self)
        Test1_1.triggered.connect(self.Test)
        EditMenu.addAction(Test1_1)

        Test2_1=qtw.QAction("Test2-1", self)
        Test2_1.triggered.connect(self.Test)
        HelpMenu.addAction(Test2_1)

    def CreateFrames(self):
        TheFrame=qtw.QWidget(self)
        TheFrame.setStyleSheet("background-color:blue")
        self.setCentralWidget(TheFrame)

        TheLayout=qtw.QVBoxLayout()
        TheFrame.setLayout(TheLayout)
        MainFrame, ButtonsFrame = qtw.QFrame(), qtw.QFrame()
        MainFrame.setStyleSheet("background-color:red"), ButtonsFrame.setStyleSheet("background-color:green")
        TheLayout.addWidget(MainFrame)
        TheLayout.addWidget(ButtonsFrame)

        # the "resize" method doesn't work..
        self.Button1=qtw.QPushButton("Button 2")
        self.Button1.resize(50,100)
        self.Button2=qtw.QPushButton("Button 2")
        self.Button2.resize(50,100)
        self.Button3=qtw.QPushButton("Button 2")
        self.Button3.resize(50,100)

        ButtonsLayout=qtw.QHBoxLayout()
        ButtonsFrame.setLayout(ButtonsLayout)
        ButtonsLayout.addWidget(self.Button1)
        ButtonsLayout.addStretch()
        ButtonsLayout.addWidget(self.Button2)
        ButtonsLayout.addWidget(self.Button3)

    def Test(self):
        pass


if __name__ == '__main__':
    app = qtw.QApplication(sys.argv)
    mw = MainWindow()
    sys.exit(app.exec())

在主要 window 内,我创建了 TheFrame(蓝框),在其中,我也创建了 ButtonsFrameMainFrame(绿框和红色的)。我的问题是:

  1. 我的做法是否正确?我的意思是,PyQt 让我创建 TheFrame 吗?因为我想,直接在mainwindow中创建ButtonsFrameMainFrame不是更好吗?我试过了,但没用。我无法在 QMainWindow 小部件中创建新布局,有一个默认布局,但我不知道它的性质。这是什么布局?

  2. 当我将 ButtonsFrameMainFrame 放入 TheFrame 时,我意识到小部件之间存在一些默认空间(我在所附的屏幕截图中对它们进行了签名).如何手动管理这些默认空间的大小?我在放置按钮小部件时看到了相同的空格..

  3. 当我使用 QHBoxLayout 对象时,如何为放置在其中的框架设置静态高度?例如,在我的代码中我想为 ButtonsFrame 设置一个默认高度,在我看来它没有向上扩展。我该怎么做?

  4. 如何更改按钮的大小?如您所见,resize 方法不起作用..

附截图:

TL; DR; 它要求的一切都在 the Qt docs 中。 Qt 文档是最好的文档之一,因为它们清楚地指出了限制和所涵盖的功能,因此建议任何 Qt 用户阅读它。


  1. 如果您检查 the docs of QMainWindow 它清楚地表明这个小部件有一个预定义的结构(检查 link 以获得更多信息以及这个问题已经在 SO 中多次出现).

    如果您不想要该默认结构,请使用 QWidget 而不是 QMainWindow,它是构建任何类型的小部件的基本构建块。

  2. 可以使用 QHBoxLayout 的 setContentsMargins() and setSpacing() 方法修改这些空间。

    TheLayout.setContentsMargins(10, 20, 30, 40)
    TheLayout.setSpacing(50)
    
  3. 布局是设置它的父小部件的子小部件的几何形状(位置和大小)的管理者,因此它们使用附加信息(例如 sizeHint、sizePolicy、拉伸因子、 ETC)。所以如果你想设置一个固定的高度然后在容器中设置那个高度:

    ButtonsFrame.setFixedHeight(300)
    

    或者在这种情况下,最好通过将系数设置为 1 来使顶部小部件尽可能地伸展。

    TheLayout.addWidget(MainFrame, stretch=1)
    TheLayout.addWidget(ButtonsFrame)
    
  4. 由于按钮的几何形状由布局处理,因此使用 resize() 不会更改任何内容,因为布局会将其恢复为默认大小。为避免这种情况,最好使用 setFixedSize():

    self.Button1 = qtw.QPushButton("Button 2")
    self.Button1.setFixedSize(50, 100)
    self.Button2 = qtw.QPushButton("Button 2")
    self.Button2.setFixedSize(50, 100)
    self.Button3 = qtw.QPushButton("Button 2")
    self.Button3.setFixedSize(50, 100)
    

QMainWindow(一种非常特殊的 QWidget,它是 Qt 中所有 ui 元素的基础)不允许设置您自己的布局,因为它有自己的 private 布局,用于管理通常在“main window”中创建的所有内容。您可以从 QMainWindow documentation:

中看到这一点

因为只要调整 window(或其任何内容)的大小时,这些对象都需要自动定位和调整大小,并且由于这种定位是 quite 复杂的性质布局在内部管理的一些小部件(停靠小部件和工具栏)。

这给您留下了创建“仅一个”中央小部件的“唯一”可能性。但这没什么大不了的:通常的做法是创建一个空的 QWidget 作为 容器 以防你的 UI 有多个“主要”小部件,就像在你的案例.

您在两个内部框架周围看到的边框是为任何布局默认创建的,可以使用 setContentsMargins(). You can use getContentsMargins() 控制以获取布局的当前默认页边距,然后根据需要更改它们。例如,如果你只想避开顶部边框,万一有一个:

    left, top, right, bottom = self.getContentsMargins()
    if top:
        self.setContentsMargins(left, 0, right, bottom)

请注意,即使 QWidget 子classes 也有内容边距(具有相同的函数名称),可以向小部件添加额外的边距,除了那些在布局上设置,但请注意,这些边距通常仅受某些小部件(通常是容器,如 QFrame)的尊重。

您看到的 这些框架之间的边框由布局的 间距 属性 控制。对于所有布局,它可以获取 (getSpacing()) 和设置 (setSpacing()) 作为全局值,为元素之间的所有间距保留,但也可以为垂直或水平间距单独设置QGridLayout (setHorizontalSpacing(), setVerticalSpacing()).

更改小部件的大小有点复杂。布局管理器实际上是布局的管理器,这意味着由他们决定元素有多大以及它们应该放在哪里,因此在由布局管理的小部件上使用 resize() 是完全没有意义的。

当小部件由布局管理时,布局会查询其 sizePolicy(),这是一个描述当 Qt 必须决定其几何形状时小部件应如何行为的对象。

有些小部件的其中一个尺寸是固定的(按钮的垂直尺寸就是这种情况,默认情况下只能水平展开),其他小部件有 expanding 策略(这意味着他们会尝试占据尽可能多的 space),而其他人则有 首选 大小(因此他们会尽可能地尝试增长,但如果在相同的layout 有一个 expanding 元素,它将优先。

然后所有这些都基于小部件的 sizeHint(),这是正常情况下小部件的 首选 大小(并且可以 return不同的值取决于情况和小部件类型),并且布局将使用它来决定如何根据小部件的策略设置几何图形。

可以通过多种方式覆盖该策略:

  • 通过为小部件设置大小策略;
  • 通过获取小部件的大小策略,更改其属性之一,然后将其设置回小部件(单独更改大小策略不会影响小部件,因为大小策略不是发送的对象“通知”);
  • 通过设置最小或最大大小限制(使用 set(Minimum|Maximum|Fixed)(Width|Height|Size));
  • 将小部件添加到布局 设置 stretch 值,这是一个整数值,告诉布局 该元素与其他元素的比例;例如,如果您添加一个带有 addWidget(button1, stretch=1) 的按钮和另一个带有 addWidget(button2, stretch=2) 的按钮,第二个按钮的大小将是第一个的两倍;

最后,一个重要的考虑因素。
Qt 布局允许 嵌套 布局:例如,如果您不喜欢 QGridLayout 提供的 rows/columns 排列,您可以将水平布局添加到垂直布局。然后你可以在那个水平布局上添加另一个垂直布局。发生这种情况是因为添加到布局的所有项目实际上都是 QLayoutItem 对象,这些对象是用于表示添加到布局的“对象”的抽象项目:当使用 addWidget() 添加小部件时,布局会创建一个QLayoutItem 为它。
另请注意,每个 QLayout class 实际上是一个 QLayoutItem subclass;当您向框布局(或拉伸)添加间距时,您实际上是在添加具有固定或扩展大小策略的 QSpacerItem(同样,QLayoutItem subclass)。

题目比较复杂,我能理解如果从一个稍微简单的框架来,你可能会感到困惑,所以我建议你慢慢来,仔细阅读关于以下主题:

我还建议您使用 Qt Designer 大量 代码 实验,这将使您更好了解 Qt 布局管理的工作原理。
它并不总是完整的ui,所以请耐心等待并始终关注文档。

小提示:如果不存在子控件,设计器不允许为 控件设置布局。一个常见的错误是创建一个 main window,然后添加一个 widget 容器并为该容器设置布局;由于 Designer 在创建主 windows 时已经设置了中央小部件(并且无法更改),结果是创建的容器不会调整其大小以适应主 window。只需确保 centralwidget 具有布局(您可以从对象检查器中的图标中看到它)。如果您想为小部件设置布局但仍需要将其留空(因为您只想从代码中添加子小部件),只需将任意小部件添加到父部件,设置布局,然后删除该小部件。