Python 和 PyQt:创建菜单、工具栏和状态栏

目录

在使用 Python 和PyQt开发图形用户界面 (GUI)应用程序时,您将使用的一些最有用和最通用的图形元素是菜单工具栏状态栏

菜单和工具栏可以使您的应用程序看起来精美专业,为用户提供一组可访问的选项,而状态栏允许您显示有关应用程序状态的相关信息。

在本教程中,您将学习:

  • 什么菜单工具栏状态栏
  • 如何以编程方式创建菜单、工具栏和状态栏
  • 如何使用PyQt 操作填充 Python 菜单和工具栏
  • 如何使用状态栏显示状态信息

此外,您将学习一些编程最佳实践,您可以在使用 Python 和 PyQt 创建菜单、工具栏和状态栏时应用这些实践。如果您不熟悉使用 PyQt 进行 GUI 编程,那么您可以查看Python 和 PyQt:构建 GUI 桌面计算器

您可以通过单击下面的框下载将在本教程中构建的示例应用程序的代码和资源:

在 PyQt 中构建 Python 菜单栏、菜单和工具栏

一个菜单栏是一个GUI应用程序的区域主窗口中保存菜单。菜单是选项的下拉列表,可以方便地访问应用程序的选项。例如,如果您正在创建一个文本编辑器,那么您的菜单栏中可能会有以下一些菜单:

  • 一个文件菜单,提供以下的一些菜单选项:
    • 新建用于创建新文档
    • 打开以打开现有文档
    • 打开最近打开最近的文档
    • Save用于保存文档
    • Exit退出应用程序
  • 提供以下一些菜单选项的“编辑”菜单:
    • Copy用于复制一些文本
    • Paste用于粘贴一些文本
    • Cut用于剪切一些文本
  • 一个帮助菜单,提供以下一些菜单选项:
    • 帮助内容用于启动用户手册和帮助内容
    • 关于启动关于对话框

您还可以将其中一些选项添加到工具栏。工具栏是带有有意义图标的按钮面板,可提供对应用程序中最常用选项的快速访问。在您的文本编辑器示例中,您可以向工具栏添加NewOpenSaveCopyPaste等选项。

注意:在本教程中,您将开发一个实现上述所有菜单和选项的示例应用程序。您可以使用此示例应用程序作为创建文本编辑器项目的起点。

在本节中,您将学习如何使用 Python 和 PyQt 向 GUI 应用程序添加菜单栏、菜单和工具栏的基础知识。

在继续之前,您将创建一个示例 PyQt 应用程序,您将在本教程中使用该应用程序。在每个部分中,您将向此示例应用程序添加新特性和功能。该应用程序将是一个主窗口风格的应用程序。这意味着它将有一个菜单栏、一个工具栏、一个状态栏和一个中央小部件。

打开您最喜欢的代码编辑器或 IDE并创建一个名为sample_app.py. 然后在其中添加以下代码:

import sys

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow

class Window(QMainWindow):
    """Main Window."""
    def __init__(self, parent=None):
        """Initializer."""
        super().__init__(parent)
        self.setWindowTitle("Python Menus & Toolbars")
        self.resize(400, 200)
        self.centralWidget = QLabel("Hello, World")
        self.centralWidget.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.setCentralWidget(self.centralWidget)

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

现在sample_app.py包含创建示例 PyQt 应用程序所需的所有代码。在这种情况下,Window继承自QMainWindow. 因此,您正在构建一个主窗口样式的应用程序。

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

在类初始化程序中.__init__(),您首先使用 调用父类的初始化程序super()。然后使用 设置窗口的标题.setWindowTitle()并使用调整窗口大小.resize()

注意:如果您不熟悉 PyQt 应用程序以及如何创建它们,那么您可以查看Python 和 PyQt:构建 GUI 桌面计算器

窗口的中央小部件是一个QLabel对象,您将使用它来显示消息以响应某些用户操作。这些消息将显示在窗口的中央。要做到这一点,你叫.setAlignment()QLabel对象与一对夫妇的对齐标志

如果您从命令行运行该应用程序,那么您将在屏幕上看到以下窗口:

PyQt 示例应用程序

就是这样!您已经使用 Python 和 PyQt 创建了一个主窗口风格的应用程序。您将在本教程中即将出现的所有示例中使用此示例应用程序。

创建菜单栏

在 PyQt 主窗口风格的应用程序中,默认QMainWindow提供一个空QMenuBar对象。要访问此菜单栏,您需要调用.menuBar()您的QMainWindow对象。此方法将返回一个空的菜单栏。此菜单栏的父级将是您的主窗口对象。

现在返回到您的示例应用程序并在 的定义中添加以下方法Window

class Window(QMainWindow):
    # Snip...
    def _createMenuBar(self):
        menuBar = self.menuBar()

这是在 PyQt 中创建菜单栏的首选方式。在这里,menuBar变量将包含一个空的菜单栏,这将是您的主窗口的菜单栏。

注意: PyQt 编程中的一个常见做法是将局部变量用于您不会使用或从其定义方法之外需要的对象。Python垃圾收集所有超出范围的对象,因此您可能认为menuBar在上面的示例中,一旦._createMenuBar() 返回就会消失。

事实是 PyQt 保留对本地对象的引用,例如menuBar使用它们的所有权或父子关系。换句话说,由于menuBar它归您的主窗口对象所有,Python 将无法对其进行垃圾收集。

向 PyQt 应用程序添加菜单栏的另一种方法是创建一个QMenuBar对象,然后使用.setMenuBar(). 考虑到这一点,您还可以._createMenuBar()按以下方式编写:

from PyQt5.QtWidgets import QMenuBar
# Snip...

class Window(QMainWindow):
    # Snip...
    def _createMenuBar(self):
        menuBar = QMenuBar(self)
        self.setMenuBar(menuBar)

在上面的例子中,menuBar持有一个QMenuBar父级设置为的对象self,它是应用程序的主窗口。一旦你有了菜单栏对象,你就可以.setMenuBar()将它添加到你的主窗口中。最后,需要注意的是在这个例子中工作,你首先需要进口 QMenuBarPyQt5.QWidgets

在 GUI 应用程序中,菜单栏会根据底层操作系统显示在不同的位置:

  • Windows:在应用程序主窗口的顶部,标题栏下方
  • macOS:在屏幕顶部
  • Linux:在主窗口顶部或屏幕顶部,取决于您的桌面环境

为应用程序创建菜单栏的最后一步是._createMenuBar()从主窗口的初始化程序调用.__init__()

class Window(QMainWindow):
    """Main Window."""
    def __init__(self, parent=None):
        # Snip...
        self._createMenuBar()

如果您使用这些新更改运行示例应用程序,那么您将看不到应用程序主窗口中显示的菜单栏。那是因为您的菜单栏仍然是空的。要查看应用程序主窗口上的菜单栏,您需要创建一些菜单。这就是你接下来要学习的内容。

将菜单添加到菜单栏

菜单菜单选项的下拉列表,您可以通过单击它们或按键盘快捷键来触发。在 PyQt 中,至少有三种方法可以将菜单添加到菜单栏对象:

  1. QMenuBar.addMenu(menu)QMenu对象 ( menu)附加到菜单栏对象。它返回与此菜单关联的操作

  2. QMenuBar.addMenu(title)创建一个QMenu以字符串 ( title) 作为标题的新对象并将其附加到菜单栏。菜单栏取得菜单的所有权,该方法返回新QMenu对象。

  3. QMenuBar.addMenu(icon, title)创建并追加新的QMenu物品与icontitle一个菜单栏对象。菜单栏取得菜单的所有权,该方法返回新QMenu对象。

如果使用第一个选项,则需要先创建自定义QMenu对象。为此,您可以使用以下构造函数之一:

  1. QMenu(parent)
  2. QMenu(title, parent)

在这两种情况下,parentQWidget将持有QMenu对象的所有权。您通常会设置parent到您将在其中使用菜单的窗口。在第二个构造函数中,title将保存一个带有描述菜单选项的文本的字符串。

以下是将FileEditHelp菜单添加到示例应用程序的菜单栏的方法:

from PyQt5.QtWidgets import QMenu
# Snip...

class Window(QMainWindow):
    # Snip...
    def _createMenuBar(self):
        menuBar = self.menuBar()
        # Creating menus using a QMenu object
        fileMenu = QMenu("&File", self)
        menuBar.addMenu(fileMenu)
        # Creating menus using a title
        editMenu = menuBar.addMenu("&Edit")
        helpMenu = menuBar.addMenu("&Help")

首先,你导入 QMenuPyQt5.QtWidgets。然后在 中._createMenuBar(),使用 的前两个变体向菜单栏添加三个菜单.addMenu()。第三个变体需要一个图标对象,但您还没有学会如何创建和使用图标。您将在使用 PyQt 中的图标和资源部分中了解如何使用图标。

如果您运行示例应用程序,那么您将看到您现在有一个如下所示的菜单栏:

PyQt 菜单栏

应用程序的菜单栏有菜单FileEditHelp。当您单击这些菜单时,它们不会显示菜单选项的下拉列表。那是因为您还没有添加菜单选项。您将在使用操作填充菜单部分中了解如何向菜单添加菜单选项。

最后,请注意&包含在每个菜单标题中的与符号 ( ) 会在菜单栏显示中创建带下划线的字母。这在定义菜单和工具栏选项的键盘快捷键一节中有更详细的讨论。

创建工具栏

工具栏是保存按钮和其他部件,以提供到GUI应用的最普通的选项快速访问的可移动面板。工具栏按钮可以显示图标、文本或两者来表示它们执行的任务。PyQt 中工具栏的基类是QToolBar. 此类将允许您为 GUI 应用程序创建自定义工具栏。

当您向主窗口样式应用程序添加工具栏时,默认位置在窗口顶部。但是,您可以在以下四个工具栏区域之一中放置工具

Toolbar Area Position in Main Window
Qt.LeftToolBarArea Left side
Qt.RightToolBarArea Right side
Qt.TopToolBarArea Top
Qt.BottomToolBarArea Bottom

工具栏区域在 PyQt 中被定义为常量。如果您需要使用它们,那么您必须导入QtfromPyQt5.QtCore然后像 in 一样使用完全限定名称Qt.LeftToolBarArea

在 PyQt 中,可以通过三种方法向主窗口应用程序添加工具栏:

  1. QMainWindow.addToolBar(title)创建一个新的空QToolBar对象并将其窗口标题设置为title. 此方法将工具栏插入顶部工具栏区域并返回新创建的工具栏。

  2. QMainWindow.addToolBar(toolbar)QToolBar对象 ( toolbar) 插入顶部工具栏区域。

  3. QMainWindow.addToolBar(area, toolbar)QToolBar对象 ( toolbar) 插入指定的工具栏区域 ( area)。如果主窗口已有工具栏,则toolbar放置在最后一个现有工具栏之后。如果toolbar已经存在于主窗口中,那么它只会被移动到area.

如果您使用最后两个选项之一,那么您需要自己创建工具栏。为此,您可以使用以下构造函数之一:

  1. QToolBar(parent)
  2. QToolBar(title, parent)

在这两种情况下,parent代表QWidget将拥有工具栏所有权的对象。您通常会将工具栏所有权设置为将在其中使用工具栏的窗口。在第二个构造函数中,title将是一个带有工具栏窗口标题的字符串。PyQt 使用这个窗口标题来构建一个默认的上下文菜单,允许你隐藏和显示你的工具栏。

现在您可以返回到您的示例应用程序并将以下方法添加到Window

from PyQt5.QtWidgets import QToolBar
# Snip...

class Window(QMainWindow):
    # Snip...
    def _createToolBars(self):
        # Using a title
        fileToolBar = self.addToolBar("File")
        # Using a QToolBar object
        editToolBar = QToolBar("Edit", self)
        self.addToolBar(editToolBar)
        # Using a QToolBar object and a toolbar area
        helpToolBar = QToolBar("Help", self)
        self.addToolBar(Qt.LeftToolBarArea, helpToolBar)

首先,您QToolBarPyQt5.QtWidgets. 然后,在 中._createToolBars(),您首先使用标题创建文件工具栏.addToolBar()。接下来,您创建一个QToolBar带有标题的对象,"Edit"并使用.addToolBar()不传递工具栏区域将其添加到工具栏。在这种情况下,编辑工具栏位于顶部工具栏区域。最后,创建帮助工具栏并使用 将其放置在左侧工具栏区域Qt.LeftToolBarArea

完成这项工作的最后一步是._createToolBars()从 的初始化程序调用Window

class Window(QMainWindow):
    """Main Window."""
    def __init__(self, parent=None):
        # Snip...
        self._createToolBars()

._createToolBars()对初始化器内部的调用Window将创建三个工具栏并将它们添加到您的主窗口中。以下是您的应用程序现在的外观:

PyQt 工具栏

现在,菜单栏正下方有两个工具栏,窗口左侧有一个工具栏。每个工具栏都有一条双虚线。当您将鼠标移到虚线上时,指针会变成一只手。如果单击并按住虚线,则可以将工具栏移动到窗口上的任何其他位置或工具栏区域。

如果右键单击工具栏,PyQt 将显示一个上下文菜单,允许您根据需要隐藏和显示现有工具栏。

到目前为止,您的应用程序窗口中有三个工具栏。这些工具栏仍然是空的——您需要添加一些工具栏按钮才能使它们起作用。为此,您可以使用 PyQt actions,它们是QAction. 您将在后面的部分中学习如何在 PyQt 中创建操作。现在,您将学习如何在 PyQt 应用程序中使用图标和其他资源。

在 PyQt 中使用图标和资源

Qt库包括Qt的资源系统,这是增加的二进制文件,如图标,图像,翻译文件和其他资源对应用程序的一种便捷方式。

要使用资源系统,您需要在资源集合文件.qrc文件中列出您的资源。一个.qrc文件是一个XML包含位置,或文件路径,文件系统中的每个资源的。

假设您的示例应用程序有一个resources目录,其中包含您要在应用程序的 GUI 中使用的图标。您有NewOpen等选项的图标。您可以创建一个.qrc包含每个图标路径的文件:

<!DOCTYPE RCC><RCC version="1.0">
<qresource>
    <file alias="file-new.svg">resources/file-new.svg</file>
    <file alias="file-open.svg">resources/file-open.svg</file>
    <file alias="file-save.svg">resources/file-save.svg</file>
    <file alias="file-exit.svg">resources/file-exit.svg</file>
    <file alias="edit-copy.svg">resources/edit-copy.svg</file>
    <file alias="edit-cut.svg">resources/edit-cut.svg</file>
    <file alias="edit-paste.svg">resources/edit-paste.svg</file>
    <file alias="help-content.svg">resources/help-content.svg</file>
</qresource>
</RCC>

每个<file>条目必须包含文件系统中资源的路径。指定的路径相对于包含.qrc文件的目录。在上面的例子中,resources目录需要和.qrc文件在同一个目录下。

alias 是一个可选属性,它定义了一个简短的替代名称,您可以在代码中使用它来访问每个资源。

一旦您拥有应用程序的资源,您就可以运行pyrcc5针对您的.qrc文件的命令行工具。pyrcc5随 PyQt 一起提供,并且必须在安装 PyQt 后在您的Python 环境中完全正常运行。

pyrcc5读取一个.qrc文件并生成一个 Python 模块,其中包含所有资源的二进制代码:

$ pyrcc5 -o qrc_resources.py resources.qrc

此命令将读取resources.qrc并生成qrc_resources.py包含每个资源的二进制代码。您将能够通过导入在 Python 代码中使用这些资源qrc_resources

注意:如果运行时出现问题pyrcc5,请确保您使用的是正确的 Python 环境。如果您在 Python 虚拟环境中安装 PyQt,那么您将无法pyrcc5在该环境之外使用。

qrc_resources.py是对应于您的代码片段resources.qrc

# -*- coding: utf-8 -*-

# Resource object code
#
# Created by: The Resource Compiler for PyQt5 (Qt v5.9.5)
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore

qt_resource_data = b"\
\x00\x00\x03\xb1\
\x3c\
\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
...

随着qrc_resources.py在地方,你可以将其导入到你的应用程序,并通过键入一个冒号(请参阅各资源:),然后无论是它的alias或它的路径。例如,要使用file-new.svg其别名进行访问,您可以使用访问字符串 ":file-new.svg"。如果您没有alias,则可以通过带有访问字符串的路径访问它":resources/file-new.svg"

如果您有别名,但由于某种原因您想通过其路径访问给定资源,那么您可能必须从访问字符串中删除冒号才能使其正常工作。

要在您的操作中使用图标,您首先需要导入您的资源模块:

import qrc_resources

导入包含资源的模块后,您可以在应用程序的 GUI 中使用这些资源。

注意: Linters编辑器和 IDE可能会将上述 import 语句标记为未使用,因为您的代码不会包含对它的任何显式使用。某些 IDE 可能会更进一步并自动删除该行。

在这些情况下,您必须覆盖您的 linter、编辑器或 IDE 的建议,并将该导入保留在您的代码中。否则,您的应用程序将无法显示您的资源。

要使用资源系统创建图标,您需要实例化QIcon,将别名或路径传递给类构造函数:

newIcon = QIcon(":file-new.svg")

在此示例中,您将QIcon使用文件创建一个对象,该对象file-new.svg位于您的资源模块中。这提供了一种在整个 GUI 应用程序中使用图标和资源的便捷方式。

现在返回到您的示例应用程序并更新最后一行._createMenuBar()

from PyQt5.QtGui import QIcon

import qrc_resources
# Snip...

class Window(QMainWindow):
    # Snip...
    def _createMenuBar(self):
        menuBar = self.menuBar()
        # Using a QMenu object
        fileMenu = QMenu("&File", self)
        menuBar.addMenu(fileMenu)
        # Using a title
        editMenu = menuBar.addMenu("&Edit")
        # Using an icon and a title
        helpMenu = menuBar.addMenu(QIcon(":help-content.svg"), "&Help")

要使此代码正常工作,您首先需要QIconPyQt5.QtGui. 您还需要导入qrc_resources. 在最后突出显示的行中,您从资源模块中添加了一个helpMenu使用图标help-content.svg

如果您使用此更新运行示例应用程序,您将获得以下输出:

带有图标的 PyQt 菜单栏

应用程序的主窗口现在在其帮助菜单上显示一个图标。当您单击该图标时,菜单会显示文本Help。在菜单栏中使用图标并不常见,但 PyQt 允许您这样做。

在 PyQt 中为 Python 菜单和工具栏创建操作

PyQt动作是表示应用程序中给定命令、操作或动作的对象。当您需要为不同的 GUI 组件(例如菜单选项、工具栏按钮和键盘快捷键)提供相同的功能时,它们非常有用。

您可以通过实例化QAction. 创建操作后,您需要将其添加到小部件中才能在实践中使用它。

您还需要将您的操作与某些功能联系起来。换句话说,您需要将它们连接到触发操作时要运行的函数方法。这将允许您的应用程序执行操作以响应 GUI 中的用户操作。

行动是相当多才多艺的。它们允许您跨菜单选项、工具栏按钮和键盘快捷键重复使用并保持同步相同的功能。这在整个应用程序中提供了一致的行为。

例如,当用户单击打开...菜单选项、单击打开工具栏按钮或按键盘上的Ctrl+O时,他们可能希望应用程序执行相同的操作。

QAction 提供了一个抽象,允许您跟踪以下元素:

  • 菜单选项上的文字
  • 工具栏按钮上的文本
  • 工具栏选项上的帮助提示(工具提示
  • 这是什么帮助提示
  • 状态栏上的帮助提示(状态提示
  • 与选项关联的键盘快捷键
  • 与菜单和工具栏选项相关联的图标
  • 动作enableddisabled状态
  • 动作onoff状态

要创建操作,您需要实例化QAction. 至少有三种通用方法可以做到这一点:

  1. QAction(parent)
  2. QAction(text, parent)
  3. QAction(icon, text, parent)

在所有三种情况下,都parent表示拥有操作所有权的对象。此参数可以是任何QObject. 最佳实践是将操作创建为您将在其中使用它们的窗口的子项。

在第二个和第三个构造函数中,text保存操作将在菜单选项或工具栏按钮上显示的文本。

操作文本在菜单选项和工具栏按钮上的显示方式不同。例如,文本&Open...显示为打开...菜单中的选项,如打开的工具栏按钮。

在第三个构造函数中,icon是一个QIcon保存动作图标的对象。此图标将显示在菜单选项中文本的左侧。图标在工具栏按钮中的位置取决于工具栏的.toolButtonStyle属性,可以采用以下值之一:

Style Button Display
Qt.ToolButtonIconOnly Only the icon
Qt.ToolButtonTextOnly Only the text
Qt.ToolButtonTextBesideIcon Text beside the icon
Qt.ToolButtonTextUnderIcon Text under the icon
Qt.ToolButtonFollowStyle Follows the general style of the underlying platform

您还可以设置该操作的文本和图标通过各自的setter方法.setText().setIcon()

注意:有关QAction属性的完整列表,您可以查看文档

以下是如何使用 的不同构造函数为示例应用程序创建一些操作QAction

from PyQt5.QtWidgets import QAction
# Snip...

class Window(QMainWindow):
    # Snip...
    def _createActions(self):
        # Creating action using the first constructor
        self.newAction = QAction(self)
        self.newAction.setText("&New")
        # Creating actions using the second constructor
        self.openAction = QAction("&Open...", self)
        self.saveAction = QAction("&Save", self)
        self.exitAction = QAction("&Exit", self)
        self.copyAction = QAction("&Copy", self)
        self.pasteAction = QAction("&Paste", self)
        self.cutAction = QAction("C&ut", self)
        self.helpContentAction = QAction("&Help Content", self)
        self.aboutAction = QAction("&About", self)

在 中._createActions(),您为示例应用程序创建了一些操作。这些操作将允许您向应用程序的菜单和工具栏添加选项。

请注意,您将操作创建为实例属性,因此您可以._createActions()使用self. 这样,您就可以在菜单和工具栏上使用这些操作。

注意:在 中._createActions(),您不使用的第三个构造函数,QAction因为如果您还看不到操作,则使用图标是没有意义的。您将在使用操作填充工具栏部分中了解如何向操作添加图标。

下一步是调用._createActions()form 的初始化程序Window

class Window(QMainWindow):
    """Main Window."""
    def __init__(self, parent=None):
        # Snip...
        self._createActions()
        self._createMenuBar()
        self._createToolBars()

如果您现在运行该应用程序,那么您将不会在 GUI 上看到任何更改。这是因为在将操作添加到菜单或工具栏之前不会显示它们。请注意,您在调用._createActions()之前先调用._createMenuBar()._createToolBars()因为您将在菜单和工具栏上使用这些操作。

如果您向菜单添加操作,则该操作将成为菜单选项。如果向工具栏添加操作,则该操作将成为工具栏按钮。这就是接下来几节的主题。

在 PyQt 中为 Python 菜单添加选项

如果要向 PyQt 中的给定菜单添加选项列表,则需要使用操作。到目前为止,您已经学习了如何使用QAction. 在 PyQt 中创建菜单时,操作是一个关键组件。

在本节中,您将学习如何使用操作来填充带有菜单选项的菜单。

用动作填充菜单

要使用菜单选项填充菜单,您将使用操作。在菜单中,操作表示为一个水平选项,其中至少有一个描述性文本,如NewOpenSave等。菜单选项还可以在其左侧显示一个图标,并在其右侧显示快捷键序列,例如Ctrl+S

您可以QMenu使用向对象添加操作.addAction()。此方法有多种变体。他们中的大多数被认为是即时创建操作。在本教程中,但是,你要使用的变化.addAction()QMenu从继承QWidget。这是此变体的签名:

QWidget.addAction(action)

参数action表示QAction要添加到给定QWidget对象的对象。使用 的这种变体.addAction(),您可以预先创建您的操作,然后根据需要将它们添加到您的菜单中。

注意: QWidget还提供.addActions(). 此方法采用一系列操作并将它们附加到当前小部件对象。

使用此工具,您可以开始向示例应用程序的菜单添加操作。为此,您需要更新._createMenuBar()

class Window(QMainWindow):
    # Snip...
    def _createMenuBar(self):
        menuBar = self.menuBar()
        # File menu
        fileMenu = QMenu("&File", self)
        menuBar.addMenu(fileMenu)
        fileMenu.addAction(self.newAction)
        fileMenu.addAction(self.openAction)
        fileMenu.addAction(self.saveAction)
        fileMenu.addAction(self.exitAction)
        # Edit menu
        editMenu = menuBar.addMenu("&Edit")
        editMenu.addAction(self.copyAction)
        editMenu.addAction(self.pasteAction)
        editMenu.addAction(self.cutAction)
        # Help menu
        helpMenu = menuBar.addMenu(QIcon(":help-content.svg"), "&Help")
        helpMenu.addAction(self.helpContentAction)
        helpMenu.addAction(self.aboutAction)

通过对 的更新._createMenuBar(),您可以向示例应用程序的三个菜单添加许多选项。

现在文件菜单有四个选项:

  1. 新建用于创建新文件
  2. Open...用于打开现有文件
  3. Save用于保存对文件所做的更改
  4. 退出以关闭应用程序

编辑菜单中有三个选项:

  1. 内容复制到系统剪贴板
  2. Paste用于从系统剪贴板粘贴内容
  3. Cut用于将内容剪切到系统剪贴板

帮助菜单中有两个选项:

  1. 用于启动应用程序帮助手册的帮助内容
  2. 关于用于显示关于对话框

选项在菜单中从上到下显示的顺序对应于您在代码中添加选项的顺序。

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

带选项的 PyQt 菜单

如果您单击某个菜单,则该应用程序会显示一个包含您之前看到的选项的下拉列表。

创建 Python 子菜单

有时您需要在 GUI 应用程序中使用子菜单。子菜单是一个嵌套的菜单,当您将光标移到给定的菜单选项上时会显示该菜单。要将子菜单添加到应用程序,您需要调用.addMenu()容器菜单对象。

假设您需要在示例应用程序的Edit菜单中添加一个子菜单。您的子菜单将包含用于查找和替换内容的选项,因此您将其称为Find and Replace。该子菜单将有两个选项:

  1. 查找...以查找一些内容
  2. 替换...用于查找旧内容并将其替换为新内容

以下是将此子菜单添加到示例应用程序的方法:

class Window(QMainWindow):
    # Snip...
    def _createMenuBar(self):
        # Snip...
        editMenu.addAction(self.cutAction)
        # Find and Replace submenu in the Edit menu
        findMenu = editMenu.addMenu("Find and Replace")
        findMenu.addAction("Find...")
        findMenu.addAction("Replace...")
        # Snip...

在突出显示的第一行中,您使用on将QMenu带有文本的对象添加"Find and Replace"到“编辑”菜单。下一步是使用您迄今为止所做的操作填充子菜单。如果您再次运行示例应用程序,您将在Edit菜单下看到一个新的菜单选项:.addMenu()editMenu

PyQt 子菜单

编辑菜单现在有一个新的条目称为查找和替换。当您将鼠标悬停在这个新菜单选项上时,会出现一个子菜单,为您提供两个新选项,Find...Replace...。就是这样!您已经创建了一个子菜单。

在 PyQt 中向工具栏添加选项

在使用 Python 和 PyQt 构建 GUI 应用程序时,工具栏是一个非常有用的组件。您可以使用工具栏向您的用户提供一种快速访问应用程序中最常用选项的方法。您还可以向工具栏添加诸如旋转框组合框之类的小部件,以允许用户直接从应用程序的 GUI 修改某些属性和变量。

在以下几节中,您将学习如何使用操作向工具栏添加选项或按钮,以及如何使用.addWidget().

用动作填充工具栏

要将选项或按钮添加到工具栏,您需要调用.addAction()。在本节中,你会依靠的变化.addAction()QToolBar从继承QWidget。因此,您将.addAction()使用动作作为参数进行调用。这将允许您在菜单和工具栏之间共享您的操作。

创建工具栏时,您通常会面临决定向其中添加哪些选项的问题。通常,您只想将最常用的操作添加到工具栏。

如果返回到示例应用程序,您会记得您添加了三个工具栏:

  1. File
  2. Edit
  3. Help

文件工具栏中,您可以添加如下选项:

  • New
  • Open
  • Save

编辑工具栏中,您可以添加以下选项:

  • Copy
  • Paste
  • Cut

通常,当您要向工具栏添加按钮时,首先要选择要在每个按钮上使用的图标。这不是强制性的,但它是最佳实践。选择图标后,您需要将它们添加到相应的操作中。

以下是向示例应用程序的操作添加图标的方法:

class Window(QMainWindow):
    # Snip...
    def _createActions(self):
        # File actions
        self.newAction = QAction(self)
        self.newAction.setText("&New")
        self.newAction.setIcon(QIcon(":file-new.svg"))
        self.openAction = QAction(QIcon(":file-open.svg"), "&Open...", self)
        self.saveAction = QAction(QIcon(":file-save.svg"), "&Save", self)
        self.exitAction = QAction("&Exit", self)
        # Edit actions
        self.copyAction = QAction(QIcon(":edit-copy.svg"), "&Copy", self)
        self.pasteAction = QAction(QIcon(":edit-paste.svg"), "&Paste", self)
        self.cutAction = QAction(QIcon(":edit-cut.svg"), "C&ut", self)
        # Snip...

要将图标添加到您的操作,请更新突出显示的行。在 的情况下newAction,您使用.setIcon(). 在其余的操作中,您使用带有icon、 atitleparent对象作为参数的构造函数。

一旦您选择的操作具有图标,您可以通过调用.addAction()工具栏对象将这些操作添加到相应的工具栏:

class Window(QMainWindow):
    # Snip...
    def _createToolBars(self):
        # File toolbar
        fileToolBar = self.addToolBar("File")
        fileToolBar.addAction(self.newAction)
        fileToolBar.addAction(self.openAction)
        fileToolBar.addAction(self.saveAction)
        # Edit toolbar
        editToolBar = QToolBar("Edit", self)
        self.addToolBar(editToolBar)
        editToolBar.addAction(self.copyAction)
        editToolBar.addAction(self.pasteAction)
        editToolBar.addAction(self.cutAction)

通过此更新._createToolBars(),您可以将新建打开保存选项的按钮添加到文件工具栏。您还可以将CopyPasteCut选项的按钮添加到“编辑”工具栏。

注意:按钮在工具栏上从左到右显示的顺序对应于您在代码中添加按钮的顺序。

如果您现在运行示例应用程序,您将在屏幕上看到以下窗口:

带有按钮的 PyQt 工具栏

示例应用程序现在显示两个工具栏,每个工具栏都有几个按钮。您的用户可以单击这些按钮以快速访问应用程序最常用的选项。

注意:当您第一次._createToolBars()创建工具栏部分回信时,您创建了一个帮助工具栏。此工具栏旨在展示如何使用不同的.addToolBar().

在 的上述更新中._createToolBars(),您去掉了帮助工具栏,只是为了使示例简短明了。

请注意,由于您在菜单和工具栏之间共享相同的操作,因此菜单选项也会在其左侧显示图标,这在生产力和资源使用方面是一个巨大的胜利。这是使用 PyQt 操作通过 Python 创建菜单和工具栏的优势之一。

向工具栏添加小部件

在某些情况下,您会发现将特定小部件(如旋转框、组合框或其他)添加到工具栏很有用。一个常见的例子是大多数文字处理器使用的组合框,允许用户更改文档的字体或所选文本的大小。

要将小部件添加到工具栏,您首先需要创建小部件,设置其属性,然后调用.addWidget()工具栏对象,将小部件作为参数传递。

假设您想向示例应用程序QSpinBox的“编辑”工具栏添加一个对象,以允许用户更改某些内容的大小,可能是字体大小。您需要更新._createToolBars()

from PyQt5.QtWidgets import QSpinBox
# Snip...

class Window(QMainWindow):
    # Snip...
    def _createToolBars(self):
        # Snip...
        # Adding a widget to the Edit toolbar
        self.fontSizeSpinBox = QSpinBox()
        self.fontSizeSpinBox.setFocusPolicy(Qt.NoFocus)
        editToolBar.addWidget(self.fontSizeSpinBox)

在这里,您首先导入旋转框类。然后您创建一个QSpinBox对象,将其设置focusPolicyQt.NoFocus,最后将其添加到您的编辑工具栏。

注意:在上面的代码中,您将focusPolicy旋转框的属性设置为,Qt.NoFocus因为如果此小部件获得焦点,则应用程序的键盘快捷键将无法正常工作。

现在,如果您运行该应用程序,那么您将获得以下输出:

带有小部件的 PyQt 工具栏

此处,“编辑”工具栏显示了一个QSpinBox对象,您的用户可以使用该对象来设置应用程序上的字体大小或任何其他数字属性。

自定义工具栏

PyQt 工具栏非常灵活且可定制。您可以在工具栏对象上设置一堆属性。下表显示了一些最有用的属性:

Property Feature Controlled Default Setting
allowedAreas The toolbar areas in which you can place a given toolbar Qt.AllToolBarAreas
floatable Whether you can drag and drop the toolbar as an independent window True
floating Whether the toolbar is an independent window True
iconSize The size of the icons displayed on the toolbar buttons Determined by the application’s style
movable Whether you can move the toolbar within the toolbar area or between toolbar areas True

所有这些属性都有一个关联的 setter 方法。例如,您可以使用.setAllowedAreas()to set allowedAreas.setFloatable()to setfloatable等。

现在,假设您不希望用户在窗口周围移动文件工具栏。在这种情况下,您可以设置movableFalse使用.setMovable()

class Window(QMainWindow):
    # Snip...
    def _createToolBars(self):
        # File toolbar
        fileToolBar = self.addToolBar("File")
        fileToolBar.setMovable(False)
        # Snip...

突出显示的线使这里变得神奇。现在您的用户无法在应用程序窗口周围移动工具栏:

PyQt 工具栏自定义

文件的工具栏不显示双虚线了,所以你的用户将无法将其移动。请注意,编辑工具栏仍然是可移动的。您可以使用相同的方法更改工具栏上的其他属性,并根据您的需要自定义它们。

组织菜单和工具栏选项

为了在 GUI 应用程序中增加清晰度并改善用户体验,您可以使用分隔符来组织菜单选项和工具栏按钮。分隔符呈现为分隔或分隔菜单选项的水平线或分隔工具栏按钮的垂直线。

要在菜单、子菜单或工具栏对象中插入或添加分隔符,您可以调用.addSeparator()这些对象中的任何一个。

例如,您可以使用分隔符将“文件”菜单上的“退出”选项与其余选项分开,以明确“退出”与菜单上​​的其余选项在逻辑上无关。您还可以使用分隔符将“编辑”菜单上的“查找和替换”选项与遵循相同规则的其余选项分开。

转到您的示例应用程序并._createMenuBar()按照以下代码进行更新:

class Window(QMainWindow):
    # Snip...
    def _createMenuBar(self):
        # File menu
        # Snip...
        fileMenu.addAction(self.saveAction)
        # Adding a separator
        fileMenu.addSeparator()
        fileMenu.addAction(self.exitAction)
        # Edit menu
        # Snip...
        editMenu.addAction(self.cutAction)
        # Adding a separator
        editMenu.addSeparator()
        # Find and Replace submenu in the Edit menu
        findMenu = editMenu.addMenu("Find and Replace")
        # Snip...

在突出显示的第一行中,在“文件”菜单中的“保存”和“退出”选项之间添加一个分隔符。在第二个突出显示的行中,添加一个分隔符,将“查找和替换”选项与“编辑”菜单中的其余选项分开。以下是这些添加的工作原理:

带分隔符的 PyQt 菜单

您的“文件”菜单现在显示一条水平线,将“编辑”选项与菜单中的其余选项分开。在编辑菜单中还显示,在选项的下拉列表中的最后一个分隔符。分隔符的连贯使用可以巧妙地提高菜单和工具栏的清晰度,使您的 GUI 应用程序更加用户友好。

作为练习,您可以转到 的定义._createToolBars()并添加一个分隔符,将QSpinBox对象与工具栏上的其余选项分开。

在 PyQt 中构建上下文或弹出菜单

上下文菜单,也称为弹出菜单,是一种特殊类型的菜单,它会响应某些用户操作(例如右键单击给定的小部件或窗口)而出现。这些菜单提供了一小部分选项,这些选项在您使用的操作系统或应用程序的给定上下文中可用。

例如,如果您右键单击 Windows 计算机的桌面,您将获得一个菜单,其中包含与操作系统的特定上下文或空间相对应的选项。如果您右键单击文本编辑器的工作区,您将获得一个完全不同的上下文菜单,具体取决于您使用的编辑器。

在 PyQt 中,您有多种创建上下文菜单的选项。在本教程中,您将了解其中两个选项:

  1. contextMenuPolicy特定小部件的属性设置为Qt.ActionsContextMenu

  2. 通过处理应用程序窗口上的上下文菜单事件contextMenuEvent()

第一个选项是两者中最常见和用户友好的,因此您将首先了解它。

第二个选项稍微复杂一些,并且依赖于处理用户事件。在 GUI 编程中,事件是应用程序上的任何用户操作,例如单击按钮或菜单、从组合框中选择项目、在文本字段中输入或更新文本、按下键盘上的键等.

通过上下文菜单策略创建上下文菜单

所有派生自的 PyQt 图形组件或小部件都QWidget继承了一个名为contextMenuPolicy. 此属性控制小部件如何显示上下文菜单。此属性最常用的值之一是Qt.ActionsContextMenu。这使得小部件将其内部操作列表显示为上下文菜单。

要使小部件根据其内部操作显示上下文菜单,您需要运行两个步骤:

  1. 使用 向小部件添加一些操作QWidget.addAction()

  2. 设置contextMenuPolicyQt.ActionsContextMenu上使用的小工具.setContextMenuPolicy()

设置contextMenuPolicyQt.ActionsContextMenu使具有操作的小部件在上下文菜单中显示它们。这是使用 Python 和 PyQt 创建上下文菜单的一种非常快速的方法。

使用这种技术,您可以向示例应用程序的中央小部件添加上下文菜单,并为您的用户提供一种快速访问某些应用程序选项的方法。为此,您可以将以下方法添加到Window

class Window(QMainWindow):
    # Snip...
    def _createContextMenu(self):
        # Setting contextMenuPolicy
        self.centralWidget.setContextMenuPolicy(Qt.ActionsContextMenu)
        # Populating the widget with actions
        self.centralWidget.addAction(self.newAction)
        self.centralWidget.addAction(self.openAction)
        self.centralWidget.addAction(self.saveAction)
        self.centralWidget.addAction(self.copyAction)
        self.centralWidget.addAction(self.pasteAction)
        self.centralWidget.addAction(self.cutAction)

在 中._createContextMenu(),您首先设置contextMenuPolicyQt.ActionsContextMenu使用 setter 方法.setContextMenuPolicy()。然后.addAction()像往常一样向小部件添加操作。最后一步是._createContextMenu()从 的初始化程序调用Window

class Window(QMainWindow):
    """Main Window."""
    def __init__(self, parent=None):
        # Snip...
        self._createToolBars()
        self._createContextMenu()

如果您在添加这些内容后运行示例应用程序,那么当您右键单击该应用程序的中央小部件时,您会看到它显示一个上下文菜单:

PyQt 上下文菜单策略

现在,您的示例应用程序有一个上下文菜单,只要您右键单击应用程序的中央小部件,就会弹出该菜单。中央小部件伸展以占据窗口中的所有可用空间,因此您不仅限于右键单击标签文本以查看上下文菜单。

最后,由于您在整个应用程序中使用相同的操作,上下文菜单上的选项显示相同的图标集。

通过事件处理创建上下文菜单

在 PyQt 中创建上下文菜单的另一种方法是处理应用程序主窗口的上下文菜单事件。为此,您需要运行以下步骤:

  1. 覆盖对象.contextMenuEvent()上的事件处理程序方法QMainWindow

  2. 创建一个QMenu传递小部件(上下文小部件)作为其父对象的对象。

  3. 用动作填充菜单对象。

  4. 使用QMenu.exec()事件.globalPos()作为参数启动菜单对象。

这种管理上下文菜单的方式有点复杂。但是,它使您可以很好地控制调用上下文菜单时发生的情况。例如,您可以根据应用程序的状态等启用或禁用菜单选项。

注意:在继续本节之前,您需要禁用您在上一节中编写的代码。为此,只需转到的初始化程序Window并注释掉调用self._createContextMenu().

以下是如何重新实现示例应用程序的上下文菜单,覆盖主窗口对象上的事件处理程序方法:

class Window(QMainWindow):
    # Snip...
    def contextMenuEvent(self, event):
        # Creating a menu object with the central widget as parent
        menu = QMenu(self.centralWidget)
        # Populating the menu with actions
        menu.addAction(self.newAction)
        menu.addAction(self.openAction)
        menu.addAction(self.saveAction)
        menu.addAction(self.copyAction)
        menu.addAction(self.pasteAction)
        menu.addAction(self.cutAction)
        # Launching the menu
        menu.exec(event.globalPos())

在 中contextMenuEvent(),您首先创建一个QMenu对象 ( menu)centralWidget作为其父小部件。接下来,您使用.addAction. 最后,调用.exec()QMenu的对象,以显示在屏幕上。

的第二个参数.contextMenuEvent()表示该方法捕获的事件。在这种情况下,event将右键单击应用程序的中央小部件。

在对 的调用中.exec(),您将其event.globalPos()用作参数。当用户单击 PyQt 窗口或小部件时,此方法返回鼠标指针的全局位置。鼠标位置将告诉.exec()窗口上显示上下文菜单的位置。

如果您使用这些新更改运行示例应用程序,那么您将获得与上一节中相同的结果。

组织上下文菜单选项

与菜单和工具栏不同,在上下文菜单中,您不能使用.addSeparator()添加分隔符并根据它们之间的关系在视觉上分隔菜单选项。在组织上下文菜单时,您需要创建一个分隔符操作:

separator = QAction(parent)
separator.setSeparator(True)

.setSeparator(True)对动作对象的调用将把该动作变成一个分隔符。完成分隔符操作后,您需要使用 将其插入上下文菜单中的正确位置QMenu.addAction()

如果您回顾一下您的示例应用程序,那么您可能希望在视觉上将来自File菜单的选项与来自Edit菜单的选项分开。为此,您可以更新.contextMenuEvent()

class Window(QMainWindow):
    # Snip...
    def contextMenuEvent(self, event):
        # Snip...
        menu.addAction(self.saveAction)
        # Creating a separator action
        separator = QAction(self)
        separator.setSeparator(True)
        # Adding the separator to the menu
        menu.addAction(separator)
        menu.addAction(self.copyAction)
        # Snip...

在前两行突出显示的行中,您创建了分隔符操作。在第三个突出显示的行中,您使用 将分隔符操作添加到菜单中.addAction()

这将在文件选项和编辑选项之间添加一条水平线。以下是添加此内容的上下文菜单的外观:

带分隔符的 PyQt 上下文菜单

现在,您的上下文菜单包含一条水平线,可直观地将来自File的选项与来自Edit的选项分开。这样,您改进了菜单的视觉质量并提供了更好的用户体验。

在菜单和工具栏中连接信号和插槽

在 PyQt 中,您使用信号和槽为 GUI 应用程序提供功能。每次在PyQt 小部件上发生诸如鼠标单击、按键或窗口大小调整等事件时,它们都会发出信号

一个插槽是一个Python可调用,您可以连接到一个小部件的信号,以响应用户事件执行某些操作。如果连接了一个信号和一个插槽,那么每次发出信号时都会自动调用该插槽。如果给定的信号未连接到插槽,则在发出信号时不会发生任何事情。

为了让你的菜单选项和工具栏按钮在用户点击它们时启动一些操作,你需要将底层操作的信号与一些自定义或内置插槽连接起来。

QAction物体可以发出各种信号。但是,菜单和工具栏中最常用的信号是.triggered()。每次用户单击菜单选项或工具栏按钮时都会发出此信号。要.triggered()与插槽连接,您可以使用以下语法:

action = QAction("Action Text", parent)
# Connect action's triggered() with a slot
action.triggered.connect(slot)

在这个例子中,slot是一个 Python 可调用的。换句话说,slot可以是一个函数、一个方法、一个类或一个实现 的类的实例.__call__()

您的示例应用程序中已经有一组操作。现在,您需要对每次用户单击菜单选项或工具栏按钮时调用的插槽进行编码。转到的定义Window并添加以下方法:

class Window(QMainWindow):
    # Snip...
    def newFile(self):
        # Logic for creating a new file goes here...
        self.centralWidget.setText("<b>File > New</b> clicked")

    def openFile(self):
        # Logic for opening an existing file goes here...
        self.centralWidget.setText("<b>File > Open...</b> clicked")

    def saveFile(self):
        # Logic for saving a file goes here...
        self.centralWidget.setText("<b>File > Save</b> clicked")

    def copyContent(self):
        # Logic for copying content goes here...
        self.centralWidget.setText("<b>Edit > Copy</b> clicked")

    def pasteContent(self):
        # Logic for pasting content goes here...
        self.centralWidget.setText("<b>Edit > Paste</b> clicked")

    def cutContent(self):
        # Logic for cutting content goes here...
        self.centralWidget.setText("<b>Edit > Cut</b> clicked")

    def helpContent(self):
        # Logic for launching help goes here...
        self.centralWidget.setText("<b>Help > Help Content...</b> clicked")

    def about(self):
        # Logic for showing an about dialog content goes here...
        self.centralWidget.setText("<b>Help > About...</b> clicked")

这些方法将扮演示例应用程序的插槽的角色。每次用户单击相应的菜单选项或工具栏按钮时都会调用它们。

一旦有了提供功能的插槽,就需要将它们与动作的.triggered()信号连接起来。这样,应用程序将根据用户事件执行操作。要进行这些连接,请转到示例应用程序并将以下方法添加到Window

class Window(QMainWindow):
    # Snip...
    def _connectActions(self):
        # Connect File actions
        self.newAction.triggered.connect(self.newFile)
        self.openAction.triggered.connect(self.openFile)
        self.saveAction.triggered.connect(self.saveFile)
        self.exitAction.triggered.connect(self.close)
        # Connect Edit actions
        self.copyAction.triggered.connect(self.copyContent)
        self.pasteAction.triggered.connect(self.pasteContent)
        self.cutAction.triggered.connect(self.cutContent)
        # Connect Help actions
        self.helpContentAction.triggered.connect(self.helpContent)
        self.aboutAction.triggered.connect(self.about)

此方法会将您所有操作的.triggered()信号与其各自的插槽或回调连接起来。通过此更新,您的示例应用程序将在QLabel您设置为中央小部件的对象上显示一条消息,告诉您单击了哪个菜单选项或工具栏按钮。

在 的情况下exitAction,您将其triggered()信号与内置插槽连接QMainWindow.close()。这样,如果您选择File → Exit,那么您的应用程序将关闭。

最后,转到 的初始化程序Window并添加对 的调用._connectActions()

class Window(QMainWindow):
    """Main Window."""
    def __init__(self, parent=None):
        # Snip...
        # self._createContextMenu()
        self._connectActions()

通过此最终更新,您可以再次运行该应用程序。以下是所有这些更改的工作原理:

PyQt 连接信号和插槽

如果单击菜单选项、工具栏按钮或上下文菜单选项,则应用程序窗口中央的标签会显示一条消息,指示已执行的操作。此功能在学习环境之外不是很有用,但它可以让您了解如何在用户与 GUI 交互时让您的应用程序执行现实世界的操作。

最后,当您选择File → Exit 时,应用程序将关闭,因为 的.triggered()信号exitAction已连接到内置插槽QMainWindow.close()

作为练习,您可以尝试为查找和替换子菜单中的查找...替换...选项创建自定义插槽,然后将它们的信号连接到这些插槽以使其生效。您还可以尝试使用您在本节中编写的插槽并尝试用它们做新的事情。.triggered()

动态填充 Python 菜单

为应用程序创建菜单时,有时需要使用创建应用程序 GUI 时未知的选项填充这些菜单。例如,文本编辑器中的“打开最近”菜单显示最近打开的文档列表。您无法在创建应用程序的 GUI 时填充此菜单,因为每个用户都会打开不同的文档,并且无法提前知道此信息。

在这种情况下,您需要动态填充菜单以响应用户操作或应用程序的状态。QMenu有一个称为.aboutToShow()您可以连接到自定义插槽的信号,以在菜单对象显示在屏幕上之前动态填充它。

要继续开发示例应用程序,假设您需要在文件下创建一个打开最近的子菜单,并用最近打开的文件或文档动态填充它。为此,您需要运行以下步骤:

  1. File下创建Open 最近的子菜单。
  2. 编写动态生成操作以填充菜单的自定义插槽。
  3. .aboutToShow()菜单信号与自定义插槽连接。

下面是创建子菜单的代码:

class Window(QMainWindow):
    # Snip...
    def _createMenuBar(self):
        # Snip...
        fileMenu.addAction(self.openAction)
        # Adding an Open Recent submenu
        self.openRecentMenu = fileMenu.addMenu("Open Recent")
        fileMenu.addAction(self.saveAction)
        # Snip...

在突出显示的行中,您在“文件”菜单下添加一个标题为 的子菜单"Open Recent"。这个子菜单还没有菜单选项。您需要动态创建操作以填充它。

您可以通过编写一种方法来动态创建操作并将它们添加到子菜单来实现此目的。这是一个示例,显示了您可以使用的一般逻辑:

from functools import partial
# Snip...

class Window(QMainWindow):
    # Snip...
    def populateOpenRecent(self):
        # Step 1. Remove the old options from the menu
        self.openRecentMenu.clear()
        # Step 2. Dynamically create the actions
        actions = []
        filenames = [f"File-{n}" for n in range(5)]
        for filename in filenames:
            action = QAction(filename, self)
            action.triggered.connect(partial(self.openRecentFile, filename))
            actions.append(action)
        # Step 3. Add the actions to the menu
        self.openRecentMenu.addActions(actions)

在 中.populateOpenRecent(),首先使用 删除菜单中的旧选项(如果有).clear()。然后添加用于动态创建和连接操作的逻辑。最后,您使用 将操作添加到菜单中.addActions()

for循环中,您使用functools.partial()来连接.triggered()信号 ,.openRecentFile()因为您想filename作为参数传递给.openRecentFile()。当将信号与需要额外参数的插槽连接时,这是一种非常有用的技术。要使其正常工作,您需要partial()functools.

注意:本示例第二步中的逻辑并没有真正加载最近打开的文件列表。它只是创建了list五个假设文件中的一个,其唯一目的是展示实现此技术的方法。

下一步是连接.aboutToShow()的信号.openRecentMenu.populateOpenRecent()。为此,请在末尾添加以下行._connectActions()

class Window(QMainWindow):
    # Snip...
    def _connectActions(self):
        # Snip...
        self.aboutAction.triggered.connect(self.about)
        # Connect Open Recent to dynamically populate it
        self.openRecentMenu.aboutToShow.connect(self.populateOpenRecent)

在突出显示的行中,您将.aboutToShow信号与连接.populateOpenRecent()。这可确保您的菜单在显示之前就被填充。

现在您需要编码.openRecentFile()。这是当您的用户单击任何动态创建的操作时您的应用程序将调用的方法:

class Window(QMainWindow):
    # Snip...
    def openRecentFile(self, filename):
        # Logic for opening a recent file goes here...
        self.centralWidget.setText(f"<b>{filename}</b> opened")

此方法将更新QLabel您用作示例应用程序的中央小部件的对象的文本。

以下是动态创建的子菜单在实践中的工作方式:

PyQt 动态创建的菜单

当您的鼠标指针悬停在打开最近菜单上时,菜单会发出.aboutToShow()信号。这会导致调用.populateOpenRecent(),从而创建并连接操作。如果单击文件名,您将看到中央标签相应地更改以显示消息。

定义菜单和工具栏选项的键盘快捷键

键盘快捷键是 GUI 应用程序中的一项重要功能。键盘快捷键是一个组合键,您可以在键盘上按下它以快速访问应用程序中的一些最常见选项。

以下是键盘快捷键的一些示例:

  • Ctrl+ 将C某些内容复制到剪贴板
  • Ctrl+V从剪贴板粘贴一些东西。
  • Ctrl+Z撤消上次操作。
  • Ctrl+O打开文件。
  • Ctrl+S保存文件。

在下面的部分中,您将学习如何向应用程序添加键盘快捷键以提高用户的工作效率和体验。

使用按键序列

到目前为止,您已经了解到这QAction是一个用于填充菜单和工具栏的多功能类。QAction还提供了一种用户友好的方式来定义菜单选项和工具栏按钮的键盘快捷键。

QAction实施.setShortcut(). 此方法将QKeySequence对象作为参数并返回键盘快捷键。

QKeySequence提供了几个构造函数。在本教程中,您将了解其中两个:

  1. QKeySequence(ks, format)将基于字符串的键序列 ( ks) 和格式 ( format) 作为参数并创建一个QKeySequence对象。

  2. QKeySequence(key)接受一个StandardKey常量作为参数并创建一个QKeySequence与底层平台上的键序列匹配的对象。

第一个构造函数识别以下字符串:

  • "Ctrl"
  • "Shift"
  • "Alt"
  • "Meta"

您可以通过将这些字符串与字母、标点符号、数字、命名键(UpDownHome)和功能键("Ctrl+S""Ctrl+5""Alt+Home""Alt+F4")组合来创建基于字符串的键序列。您最多可以在逗号分隔列表中传递四个基于字符串的键序列。

注:有关在不同平台上的标准快捷的完整参考,请参阅标准快捷键部分中的QKeySequence文档。

如果您正在开发多平台应用程序并希望坚持每个平台的标准键盘快捷键,则第二个构造函数很方便。例如,QKeySequence.Copy将返回用于将对象复制到剪贴板的平台标准键盘快捷键。

注意:有关 PyQt 提供的标准密钥的完整参考,请参阅QKeySequence.StandardKey 文档

有了关于如何在 PyQt 中为操作定义键盘快捷键的一般背景,您可以返回示例应用程序并添加一些快捷键。为此,您需要更新._createActions()

from PyQt5.QtGui import QKeySequence
# Snip...

class Window(QMainWindow):
    # Snip...
    def _createActions(self):
        # File actions
        # Snip...
        # Using string-based key sequences
        self.newAction.setShortcut("Ctrl+N")
        self.openAction.setShortcut("Ctrl+O")
        self.saveAction.setShortcut("Ctrl+S")
        # Edit actions
        # Snip...
        # Using standard keys
        self.copyAction.setShortcut(QKeySequence.Copy)
        self.pasteAction.setShortcut(QKeySequence.Paste)
        self.cutAction.setShortcut(QKeySequence.Cut)
        # Snip...

您首先需要导入QKeySequence. 在里面._createActions(),前三个突出显示的行使用基于字符串的键序列创建键盘快捷键。这是向您的操作添加键盘快捷键的快速方法。在后三个突出显示的行中,您用于QKeySequence提供标准键盘快捷键。

如果您运行带有这些添加的示例应用程序,那么您的菜单将如下所示:

PyQt 键盘快捷键

您的菜单选项现在会在其右侧显示键盘快捷键。如果您按这些组合键中的任何一个,那么您将执行相应的操作。

使用键盘加速器

您可以使用另一种替代方法将键盘快捷键或键盘加速器添加到应用程序的菜单选项中。

您可能已经注意到,当您为菜单或菜单选项设置文本时,通常会&在文本中插入一个与符号 ( )。这样做是为了当显示在菜单或菜单选项的文本中时,紧跟在&符号之后的字母将带有下划线。例如,如果您在“文件”菜单 ( )的标题中的字母F之前放置一个与号,则在显示菜单标题时F将带有下划线。"&File"

注意:如果您需要在菜单文本上显示与号符号,则需要使用双与号 ( &&) 来逃避此符号的默认功能。

在菜单栏的情况下,使用与号允许您通过Alt与菜单标题中带下划线的字母组合按下来调用任何菜单。

启动菜单后,您可以通过按选项文本中带下划线的字母来访问任何菜单选项。例如,在文件中,您可以通过按字母E访问退出选项。

注意:当您使用与号来提供键盘加速器时,请记住在同一菜单下不能有两个选项共享相同的访问字母。

如果您将C设置为Copy选项的访问字母,则不能将C设置为Cut选项的访问字母。换句话说,在给定的菜单下,访问字母必须是唯一的。

此功能将允许您为喜欢使用键盘来处理您的应用程序的用户提供快速键盘加速器。此技术对于不提供显式键盘快捷键的选项特别有用。

创建菜单和工具栏:最佳实践和技巧

当您使用 Python 和 PyQt 创建菜单和工具栏时,您应该遵循一些通常被认为是 GUI 编程最佳实践的标准。这是一个快速列表:

  1. 按照普遍接受的顺序排列菜单。例如,如果您有一个文件菜单,那么它应该是从左到右的第一个菜单。如果你有一个编辑菜单,那么它应该是第二个。帮助应该是最右边的菜单,依此类推。

  2. 使用您正在开发的应用程序类型的常用选项填充您的菜单。例如,在文本编辑器中,文件菜单通常包括诸如NewOpenSaveExit 之类的选项编辑菜单通常包括复制粘贴剪切撤消等选项。

  3. 对常用选项使用标准键盘快捷键。例如,使用Ctrl+C进行复制Ctrl+V用于粘贴Ctrl+X用于切割,等等。

  4. 使用分隔符分隔不相关的选项。这些视觉提示将使您的应用程序更易于导航。

  5. 将省略号 ( ...)添加到启动其他对话框的选项的标题。例如,使用Save As...而不是Save As,使用About...而不是About,等等。

  6. &在菜单选项中使用与号 ( ) 来提供方便的键盘加速器。例如,"&Open代替"Open""&Exit"代替"Exit"

如果您遵循这些准则,那么您的 GUI 应用程序将为您的用户提供熟悉且诱人的体验。

在 PyQt 中构建 Python 状态栏

状态栏是水平面板通常在GUI应用程序放置在底部的主窗口。它的主要目的是显示有关应用程序当前状态的信息。状态栏也可以分为多个部分,以显示每个部分的不同信息。

根据Qt 文档,状态指示器分为三种类型:

  1. 临时指示器会在短时间内占据几乎整个状态栏以显示工具提示文本、菜单项和其他时间敏感信息。

  2. 普通指示器占据状态栏的一部分并显示用户可能希望定期参考的信息,例如文字处理器中的字数统计。这些可能会被临时指标暂时隐藏。

  3. 永久指示器始终显示在状态栏中,即使临时指示器被激活也是如此。它们用于显示有关应用程序当前模式的重要信息,例如按下 Caps Lock 键的时间。

您可以使用以下选项之一向主窗口样式的应用程序添加状态栏:

  • 调用.statusBar()你的QMainWindow对象。.statusBar()创建并返回主窗口的空状态栏。

  • 创建一个QStatusBar对象,然后.setStatusBar()使用状态栏对象作为参数调用主窗口。这样,.setStatusBar()将您的状态栏对象设置为主窗口的状态栏。

在这里,您有两种替代实现来向示例应用程序添加状态栏:

# 1. Using .statusBar()
def _createStatusBar(self):
    self.statusbar = self.statusBar()

# 2. Using .setStatusBar()
def _createStatusBar(self):
    self.statusbar = QStatusBar()
    self.setStatusBar(self.statusbar)

两种实现产生相同的结果。但是,大多数情况下,您将使用第一个实现来创建状态栏。请注意,要使第二个实现工作,您需要QStatusBarPyQt5.QtWidgets.

将上述实现之一添加到您的应用程序Window,然后调用._createStatusBar()类初始值设定项。通过这些添加,当您再次运行您的应用程序时,您将看到一个如下所示的窗口:

PyQt 状态栏

您的应用程序现在在其主窗口底部有一个状态栏。状态栏几乎不可见,但如果仔细观察,您会注意到窗口右下角有一个小的虚线三角形。

显示临时状态消息

状态栏的主要目的是向应用程序的用户显示状态信息。要在状态栏中显示临时状态消息,您需要使用QStatusBar.showMessage(). 此方法采用以下两个参数:

  1. message 将状态指示消息作为字符串保存。
  2. timeout 保存消息将显示在状态栏上的毫秒数。

如果timeout0,这是其默认值,则消息将保留在状态栏上,直到您调用.clearMessage().showMessage()状态栏上。

如果您的状态栏上有一条活动消息并且您.showMessage()用新消息呼叫,那么新消息将掩盖或替换旧消息。

转到您的示例应用程序并将以下行添加到._createStatusBar()

class Window(QMainWindow):
    # Snip...
    def _createStatusBar(self):
        self.statusbar = self.statusBar()
        # Adding a temporary message
        self.statusbar.showMessage("Ready", 3000)

最后一行._createStatusBar()将使您的应用程序Ready在应用程序的状态栏上显示一条消息3000几毫秒:

PyQt 状态消息

运行应用程序时,状态栏会显示消息Ready。之后3000毫秒,此消息消失,状态栏被清除,并准备展现出新的状态信息。

在状态栏中显示永久消息

您还可以在应用程序的状态栏上显示永久消息。一条永久消息让用户了解应用程序的一些一般状态。例如,在文本编辑器中,您可能希望显示一条永久消息,其中包含有关当前打开文件的文本编码的信息。

要将永久消息添加到状态栏,请使用QLabel对象来保存消息。然后通过调用将标签添加到状态栏.addPermanentWidget()。此方法将给定的小部件永久添加到当前状态栏。小部件的父级设置为状态栏。

.addPermanentWidget() 采用以下两个参数:

  1. widget保存要添加到状态栏的小部件对象。这个角色的一些常用小部件QLabelQToolButton以及QProgressBar
  2. stretch用于随着状态栏的增长和收缩计算小部件的合适大小。它默认为0,这意味着小部件将占用最少的空间。

请记住,永久小部件不会被临时消息遮蔽或替换。.addPermanentWidget()在状态栏的右侧定位小部件。

注意:.addPermanentWidget()不仅可以使用在状态栏上显示永久消息,还可以向用户显示进度条以监控给定操作的持续时间。您还可以在状态栏上提供按钮,以允许用户在文本编辑器上更改文件编码等属性。

当您在状态栏上使用这些类型的小部件时,尽量坚持使用最常用的小部件来满足您正在开发的应用程序类型。这样,您的用户就会有宾至如归的感觉。

假设您想将示例应用程序转换为文本编辑器,并且您想向状态栏添加一条消息,以显示有关当前文件字数的信息。为此,您可以创建一个调用的方法.getWordCount(),然后使用.addPermanentWidget()QLabel对象添加永久消息:

class Window(QMainWindow):
    # Snip...
    def getWordCount(self):
        # Logic for computing the word count goes here...
        return 42

该方法添加了计算当前打开文档中字数的逻辑。现在,您可以将此信息显示为永久消息:

class Window(QMainWindow):
    # Snip...
    def _createStatusBar(self):
        self.statusbar = self.statusBar()
        # Adding a temporary message
        self.statusbar.showMessage("Ready", 3000)
        # Adding a permanent message
        self.wcLabel = QLabel(f"{self.getWordCount()} Words")
        self.statusbar.addPermanentWidget(self.wcLabel)

在最后两行中,您首先创建一个QLabel对象 ( wcLabel) 来保存有关字数的消息。要创建消息,请使用f-string,在其中插入对 的调用.getWordCount()以获取字数信息。然后使用 将标签添加到状态栏.addPermanentWidget()

在这种情况下,您将QLabel对象创建为实例属性,因为需要根据用户对当前文件所做的更改来更新字数。

如果您使用此更新运行应用程序,那么您将在状态栏的右侧看到字数统计消息:

带有永久小部件的 PyQt 状态栏

状态栏会显示一条消息,通知用户假设当前文件中的字数。在状态栏中向用户显示永久信息或其他选项的能力非常有用,可以帮助您极大地改善应用程序的用户体验。

向操作添加帮助提示

在创建 GUI 应用程序时,向用户提供有关应用程序界面特定功能的帮助提示非常重要。帮助提示是短消息,可为用户提供有关应用程序提供的某些选项的快速指南。

PyQt 操作允许您定义以下类型的帮助提示:

  • 状态提示是当用户将鼠标指针悬停在菜单选项或工具栏按钮上时应用程序显示在状态栏上的帮助提示。默认情况下,状态提示包含一个空字符串。

  • 工具提示是当用户将鼠标指针悬停在工具栏按钮或小部件上时应用程序显示为浮动消息的帮助提示。默认情况下,工具提示包含标识手头操作的文本。

注意: PyQt 还提供了What's This帮助提示,您可以在小部件和动作中使用它来显示对小部件或动作提供的功能的更丰富的描述。但是,该主题超出了本教程的范围。

要了解帮助提示的工作原理,您可以向示例应用程序添加一些状态提示和工具提示。转到._createActions()并添加以下代码行:

class Window(QMainWindow):
    # Snip...
    def _createActions(self):
        # File actions
        # Snip...
        self.saveAction.setShortcut("Ctrl+S")
        # Adding help tips
        newTip = "Create a new file"
        self.newAction.setStatusTip(newTip)
        self.newAction.setToolTip(newTip)
        # Edit actions
        self.copyAction = QAction(QIcon(":edit-copy.svg"), "&Copy", self)
        # Snip...

三个突出显示的行将消息设置"Create a new file"为“新建”选项的状态和工具提示。如果您现在运行该应用程序,您将看到New选项向用户显示了一个简短但描述性的帮助提示:

PyQt 帮助提示

当您单击File菜单并将鼠标指针放在New 上时,您可以看到状态栏左侧显示的帮助提示消息。另一方面,如果您将鼠标指针移到“新建”工具栏按钮上,则您可以在状态栏上看到消息,也可以在鼠标指针旁边看到一个小的浮动框。

通常,向 Python 菜单和工具栏添加帮助提示被认为是最佳实践。它将使您的 GUI 应用程序更易于用户导航和学习。作为最后的练习,您可以继续向示例应用程序的其余操作添加帮助提示,并查看完成后的效果。

结论

菜单工具栏状态栏是大多数GUI 应用程序的常见且重要的图形组件。您可以使用它们为您的用户提供一种快速访问应用程序选项和功能的方法。它们还使您的应用程序看起来精美和专业,并为您的用户提供出色的体验。

在本教程中,您学习了如何:

  • 以编程方式创建菜单工具栏状态栏
  • 使用 PyQt操作填充菜单和工具栏
  • 使用状态栏提供状态信息

在此过程中,您学习了一些在 GUI 应用程序中添加和使用菜单、工具栏和状态栏时值得考虑的最佳编程实践

您还编写了一个示例应用程序,在其中应用了您对菜单和工具栏的所有知识。您可以通过单击下面的框来获取该应用程序的完整源代码和其他资源:

(完)