查看原文
其他

用 PyQt 打造具有专业外观的GUI(上)

Leodanis Python中文社区 2022-12-04

PyQt的布局管理器layout managers提供了一种用户友好且高效的方式,可以在GUI上排列图形组件或小部件。正确布置窗口小部件将使您的GUI应用程序看起来更加优美和专业。学习有效地做到这一点是您使用Python和PyQt进行GUI应用程序开发和运行的一项基本技能。

在本教程中,您将学习:

  • 使用PyQt的layout managers有什么好处
  • 如何使用PyQt的layout managers在GUI上以编程方式布置窗口小部件
  • 如何为您的GUI应用程序选择正确的layout manager
  • 如何在基于主窗口和基于对话框的应用程序中布置小部件

有了这些知识和技能,您就可以使用Python和PyQt创建具有专业外观的GUI应用程序。

在GUI上布置图形元素

在创建图形用户界面(GUI)应用程序时,一个常见的问题是如何使图形组件(按钮,菜单,工具栏,标签等)一致地放置在窗体和窗口上。此过程称为GUI布局(GUI layout,),这是创建GUI应用程序的重要步骤。

之前如果要在窗口上布置图形组件或窗口小部件,则可以采用以下方法之一:

  • 为窗口上的每个小部件确定并手动设置静态大小和位置。
  • 动态计算并设置每个小部件的大小和位置。

第一种方法相当直接,但是至少具有以下缺点:

  • 您的窗口将无法调整大小,以不同的屏幕分辨率显示它们时可能会导致问题。
  • 您的标签可能不能正确地支持本地化,因为给定文本的长度在不同语言之间会发生变化。
  • 您的窗口小部件在不同平台上的显示方式将有所不同,这使得编写看起来不错的多平台应用程序变得困难。

第二种方法更灵活。但是,它也有缺点:

  • 您必须进行大量手动计算才能确定每个小部件的正确大小和位置。
  • 您必须做一些额外的计算才能正确响应窗口大小调整。
  • 每当您修改窗口布局时,都必须重做所有计算。

即使您仍然可以使用这两种方法中的任何一种来布局GUI,大多数时候您可能还是希望使用由最先进的GUI框架或工具包实现的第三种且更方便的方法:布局管理器layout managers。注意:在某些GUI框架(例如Tkinter)中,布局管理器也称为geometry managers

layout managers会根据您的特定需求自动在GUI上排列小 部件。它避免了第一种方法的兼容性缺点以及第二种方法的复杂计算。

在以下各节中,您将了解PyQt的内置布局管理器以及如何使用它们有效地布局GUI应用程序的图形组件。

多样的PyQt布局形式

在PyQt中,小部件是用作GUI应用程序构建块的图形组件。将一堆小部件放在窗口上以创建GUI时,需要给它们一些顺序。您需要设置窗口小部件的大小和在窗口上的位置,还需要定义当用户调整基础窗口大小时它们的行为。

注意:PyQt5的官方文档中有一些不完整的部分。要解决此问题,您可以查看PyQt4文档,Qt for Python文档或原始Qt文档。

要在PyQt的窗口或窗体上排列小部件,可以使用以下技术:

  • 在小部件上使用.resize().move()来提供绝对大小和位置。
  • 重新实现.resizeEvent()并动态计算小部件的大小和位置。
  • 使用布局管理器,让他们为您做所有的计算和辛苦的工作。

这些技术通常对应于上一节中介绍的用于布局GUI的三种不同方法。

再说一次,动态计算尺寸和位置可能是个好方法,但是大多数时候,使用布局管理器会更好。在PyQt中,布局管理器是提供所需功能以自动管理布局中小部件的大小,位置和调整大小行为的类。

使用布局管理器,您可以自动在任何母部件、容器、小部件内排列子部件。使用布局管理器将确保您充分利用GUI上的可用空间,并确保当用户调整窗口大小时,应用程序仍然可用。

布局管理器充当小部件和其他布局的容器时,要将小部件添加到布局管理器,请在当前布局上调用.addWidget()。要将布局添加到另一个布局,请在当前布局上调用.addLayout()。您将在“嵌套布局以构建复杂的GUI”一节中更深入地了解嵌套布局。

将所有必需的小部件添加到布局管理器后,即可使用.setLayout()在给定的小部件上设置布局管理器。您可以在QWidget的任何子类(包括窗口或窗体)上设置布局管理器。

注意:QMainWindow是一个PyQt类,可用于创建main widow–style applications.。此类具有其自己的内置布局管理器。因此,如果您使用的是QMainWindow,则通常不需要在主窗口对象上设置布局管理器。

布局中的所有窗口小部件都会自动设置为安装布局的窗口小部件的子级,而不是布局本身。这是因为窗口小部件只能将其他窗口小部件(而不是布局)作为其父级。

PyQt的布局管理器提供了一些很酷的功能,可以使您在创建美观的GUI应用程序时更加轻松:

  • 处理小部件的大小和位置,无需任何计算
  • 当用户调整基础窗口的大小时,处理窗口小部件的大小调整和重新定位
  • 调整标签大小以更好地支持国际化
  • 为多平台应用程序提供本机窗口布局

从长远来看,使用布局管理器还将极大地提高您的生产率并改善代码的可维护性。

PyQt提供了四个通用布局管理器类:

  • QHBoxLayout在水平框中排列小部件。
  • QVBoxLayout在垂直框中排列小部件。
  • QGridLayout将小部件排列在网格中。
  • QFormLayout将小部件安排在两列中。

在接下来的几节中,您将学习如何使用这些通用布局管理器的基础知识。

使用通用布局管理器

使用PyQt创建GUI应用程序时,通常会使用上一节末尾看到的四种通用布局中的一种或多种方法,来将窗口小部件布置在窗口和窗体上。

在接下来的几节中,您将在一些示例的帮助下学习如何创建和使用四个通用布局管理器。

构建水平布局:QHBoxLayout

Box layout managers从父布局或窗口小部件获得的空间被分成多个盒子或单元格,然后使布局中的每个窗口小部件填充一个盒子。

QHBoxLayout是PyQt中两个可用的框布局之一。这个布局管理器允许您水平排列小部件,一个接一个。这些小部件从左到右添加到布局中。这意味着您首先在代码中添加的小部件将是布局中最左侧的小部件。

要将小部件添加到QHBoxLayout对象,请在布局对象上调用.addWidget(widget,stretch,alignment)。此方法接受一个必需参数和两个可选参数:

  • widget是必填参数,用于保存要添加到布局的特定widget。

  • Stretch是一个可选参数,其中包含一个整数,该整数表示要应用于小部件的拉伸因子。具有较高拉伸因子的小部件在调整窗口大小时会增长更多。默认为0,这表示未分配窗口小部件拉伸因子。

  • alignment是一个可选参数,其中包含水平和垂直标志。您可以组合这些标志,以在其包含的单元格内产生所需的小部件对齐方式。默认为0,这意味着小部件将填充整个单元格。

下面是一个小型应用程序,显示了如何使用QHBoxLayout创建水平布局。在此示例中,您将使用QPushButton对象根据将小部件添加到代码中的顺序更好地可视化每个小部件在布局中的放置位置:

import sys

from PyQt5.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QPushButton,
    QWidget,
)

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QHBoxLayout Example")
        # Create a QHBoxLayout instance
        layout = QHBoxLayout()
        # Add widgets to the layout
        layout.addWidget(QPushButton("Left-Most"))
        layout.addWidget(QPushButton("Center"), 1)
        layout.addWidget(QPushButton("Right-Most"), 2)
        # Set the layout on the application's window
        self.setLayout(layout)
        print(self.children())

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

在第15行上,创建一个称为layoutQHBoxLayout对象。在第17至19行,使用.addWidget()将三个按钮添加到布局中。请注意,您分别在“居中”和“最右”按钮中将1和2传递给了stretch参数。在第21行,使用.setLayout()将布局设置为窗口的顶级布局。

如果您运行此应用程序,则会在屏幕上看到以下窗口:

该窗口包含三个水平排列的按钮。请注意,“最左键”按钮对应于您在代码中添加的第一个按钮。因此,按钮的显示顺序与您在代码中添加按钮的顺序(从左到右)(从上到下)相同。

“居中”和“最右边”按钮具有不同的拉伸系数,因此在调整窗口大小时,它们会按比例缩放。

此外,布局中的所有按钮和布局本身都设置为Window的子级。这是由布局对象自动完成的,该布局对象在每个小部件上内部调用.setParent()。在第17行上对print()的调用将在您的终端上打印Window的子代列表,以证明此行为。

构建垂直布局:QVBoxLayout

QVBoxLayout垂直排列小部件,一个在另一个下方。您可以使用此类创建垂直布局,并从上到下排列窗口小部件。由于QVBoxLayout是另一个框布局,因此其.addWidget()方法的工作方式与QHBoxLayout中的相同。

下面是一个PyQt应用程序,它显示了如何创建和使用QVBoxLayout对象在GUI中创建小部件的垂直排列:

import sys

from PyQt5.QtWidgets import (
    QApplication,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QVBoxLayout Example")
        self.resize(270, 110)
        # Create a QVBoxLayout instance
        layout = QVBoxLayout()
        # Add widgets to the layout
        layout.addWidget(QPushButton("Top"))
        layout.addWidget(QPushButton("Center"))
        layout.addWidget(QPushButton("Bottom"))
        # Set the layout on the application's window
        self.setLayout(layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

在第16行上,您将创建QVBoxLayout的实例。在第18至20行上,将三个按钮添加到布局中。最后,将布局设置为窗口的顶层布局。

如果您运行此应用程序,则将显示以下窗口:

您的窗口显示了三个垂直排列的按钮,一个在另一个按钮下面。这些按钮的显示顺序(从上到下)与您在代码中添加的顺序(从上到下)相同。

在网格中排列小部件:QGridLayout

您可以使用QGridLayout将小部件排列在行和列的网格中。每个小部件将在网格中具有相对位置。要定义小部件的位置或网格中的单元格,请使用表格(行,列)的一对坐标。这些坐标应该是从零开始的整数。

QGridLayout占用其父级的可用空间,将其划分为行和列,然后将每个小部件放置在其自己的单元格或框中。QGridLayout根据小部件的数量及其坐标自动计算出最终布局将包含多少行和多少列。如果您不将小部件添加到给定的单元格,则QGridLayout将使该单元格为空。

要将小部件添加到网格布局,请在布局上调用.addWidget()。此方法有两个不同的重载实现:

  • addWidget(widget, row, column, alignment)widget添加到(row, column)的单元格中。

  • addWidget(widget,fromRow,fromColumn,rowSpan,columnSpan,alignment)widget添加到单元格中,跨越多行,多列或两者。

第一个实现采用以下参数:

  • widget是必填参数,用于保存您需要添加到布局的特定小部件。
  • row是必填参数,其中包含一个整数,该整数表示网格中行的坐标。
  • column是必填参数,其中包含一个整数,该整数表示网格中列的坐标。
  • alignment是一个可选参数,用于保存小部件在其包含的单元格内的对齐方式。默认为0,这意味着小部件将填充整个单元格。

这是一个如何使用QGridLayout创建小部件网格的示例:

import sys

from PyQt5.QtWidgets import (
    QApplication,
    QGridLayout,
    QPushButton,
    QWidget,
)

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QGridLayout Example")
        # Create a QGridLayout instance
        layout = QGridLayout()
        # Add widgets to the layout
        layout.addWidget(QPushButton("Button at (0, 0)"), 0, 0)
        layout.addWidget(QPushButton("Button at (0, 1)"), 0, 1)
        layout.addWidget(QPushButton("Button at (0, 2)"), 0, 2)
        layout.addWidget(QPushButton("Button at (1, 0)"), 1, 0)
        layout.addWidget(QPushButton("Button at (1, 1)"), 1, 1)
        layout.addWidget(QPushButton("Button at (1, 2)"), 1, 2)
        layout.addWidget(QPushButton("Button at (2, 0)"), 2, 0)
        layout.addWidget(QPushButton("Button at (2, 1)"), 2, 1)
        layout.addWidget(QPushButton("Button at (2, 2)"), 2, 2)
        # Set the layout on the application's window
        self.setLayout(layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

在第15行上,创建QGridLayout对象。然后,在第17至25行上,使用.addWidget()将小部件添加到布局中。要查看没有分配小部件的网格布局如何管理单元,请注释掉其中的一行或多行,然后再次运行应用程序。

如果您从命令行运行此代码,则将获得如下所示的窗口:


QGridLayout对象中的每个小部件都占据由您在.addWidget()中提供的一对坐标定义的单元格。每个按钮上的文字均反映了这些坐标。坐标是从零开始的,因此第一个单元格位于(0,0)

.addWidget()的第二种实现中,widgetalignment参数保持不变,并且您还有四个其他参数可用于将小部件放置在多行或多列中:

  • fromRow赋值一个整数,该整数表示小部件将在其中开始的行。
  • fromColumn赋值一个整数,该整数表示小部件将在其中开始的列。
  • rowSpan赋值一个整数,该整数表示窗口小部件将在网格中占据的行数。
  • columnSpan赋值一个整数,该整数表示窗口小部件将在网格中占据的列数。

这是一个显示.addWidget()变体如何工作的应用程序:

import sys

from PyQt5.QtWidgets import (
    QApplication,
    QGridLayout,
    QPushButton,
    QWidget,
)

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QGridLayout Example")
        # Create a QGridLayout instance
        layout = QGridLayout()
        # Add widgets to the layout
        layout.addWidget(QPushButton("Button at (0, 0)"), 0, 0)
        layout.addWidget(QPushButton("Button at (0, 1)"), 0, 1)
        layout.addWidget(QPushButton("Button Spans two Cols"), 1, 0, 1, 2)
        # Set the layout on the application's window
        self.setLayout(layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

在第19行,使用.addWidget()的第二种实现来添加一个占用网格中两列的按钮。该按钮从第二行(fromRow = 1)和第一列(fromColumn = 0)开始。最后,该按钮占据一行(rowSpan = 1)和两列(columnSpan = 2)。

如果您运行此应用程序,则会在屏幕上看到以下窗口:

在这种布局中,您可以使一个小部件占用多个单元格,就像使用“跨越两个列”的按钮一样。

在下篇中,我们将看到:

  • 如何快速创建表单

  • 嵌套布局以构建复杂的GUI

  • 使用多页布局和小部件

  • 布置应用程序的主窗口

  • 布置应用程序的对话框

  • 在PyQt布局中管理输入框

更多阅读



有人在代码里下毒!慎用 pip install 命令


pip 20.3 新版本发布!即将抛弃 Python 2.x


5 分钟掌握 Python 中的 Hook 钩子函数


特别推荐


程序员摸鱼指南


为你精选的硅谷极客资讯,
来自FLAG巨头开发者、技术、创投一手消息




点击下方阅读原文加入社区会员



点赞鼓励一下

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存