原文:RealPython

协议:CC BY-NC-SA 4.0

用 PyQt 处理 SQL 数据库:基础知识

原文:https://realpython.com/python-pyqt-database/

构建使用 SQL 数据库的应用程序是一项相当常见的编程任务。SQL 数据库无处不在,在 Python 中有很大的支持。在 GUI 编程中,PyQt 提供了健壮的、跨平台的 SQL 数据库支持,允许您一致地创建、连接和管理您的数据库。

PyQt 的 SQL 支持与它的模型-视图架构完全集成,在构建数据库应用程序的过程中为您提供帮助。

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

  • 使用 PyQt 的 SQL 支持可靠地连接到数据库
  • 使用 PyQt 在数据库上执行 SQL 查询
  • 在数据库应用程序中使用 PyQt 的模型-视图架构
  • 使用不同的 PyQt 小部件显示和编辑数据

本教程中的例子需要 SQL 语言的基础知识,尤其是对数据库管理系统 SQLite 的基础知识。使用 Python 和 PyQt 进行 GUI 编程的一些知识也会有所帮助。

免费奖励: 掌握 Python 的 5 个想法,这是一个面向 Python 开发者的免费课程,向您展示将 Python 技能提升到下一个水平所需的路线图和心态。

将 PyQt 连接到 SQL 数据库

将一个应用程序连接到一个关系数据库并让应用程序创建、读取、更新和删除存储在该数据库中的数据是编程中的一项常见任务。关系数据库通常被组织成一组,或者关系。表中给定的称为记录元组,而称为属性

注:术语字段常用的来标识表中给定记录的单元格中存储的单个数据。另一方面,术语字段名用于标识列的名称。

每列存储一种特定的信息,如姓名、日期或数字。每一行代表一组密切相关的数据,并且每一行都具有相同的一般结构。例如,在存储公司雇员数据的数据库中,一个特定的行代表一个雇员。

大多数关系数据库系统使用 SQL(结构化查询语言)来查询、操作和维护数据库中保存的数据。SQL 是一种声明式特定于领域的编程语言,专门为与数据库通信而设计。

关系数据库系统和 SQL 现在被广泛使用。你会发现几种不同的数据库管理系统,比如 SQLitePostgreSQLMySQLMariaDB 等等。您可以使用专用的 Python SQL 库将 Python 连接到这些数据库系统中的任何一个。

**注意:**尽管 PyQt 的内置 SQL 支持是在 PyQt 中管理 SQL 数据库的首选选项,但是您也可以使用任何其他库来处理数据库连接。这些库包括 SQLAlchemypandasSQLite 等等。

然而,使用不同的库来管理数据库有一些缺点。您将无法利用 PyQt 的 SQL 类和模型-视图架构之间的集成。此外,您将向您的应用程序添加额外的依赖项。

当使用 Python 和 PyQt 进行 GUI 编程时,PyQt 提供了一组健壮的类来处理 SQL 数据库。当您需要将应用程序连接到 SQL 数据库时,这组类将是您最好的盟友。

**注:**不幸的是, PyQt5 的官方文档有一些不完整的章节。要解决这个问题,您可以查看 PyQt4 文档,Python 文档的 Qt,或者原始的 Qt 文档。在本教程中,一些链接会将您带到最初的 Qt 文档,在大多数情况下,这是一个更好的信息来源。

在本教程中,您将学习如何使用 PyQt 的 SQL 支持创建 GUI 应用程序的基础知识,该应用程序能够可靠地与关系数据库交互,以读取、写入、删除和显示数据。

Remove ads

创建数据库连接

在使用 PyQt 开发数据库应用程序的过程中,将应用程序连接到物理 SQL 数据库是一个重要的步骤。要成功执行此步骤,您需要一些关于如何设置数据库的一般信息。

例如,您需要知道您的数据库是在什么数据库管理系统上构建的,您可能还需要有用户名、密码、主机名等等。

在本教程中,您将使用 SQLite 3 ,这是一个经过良好测试的数据库系统,支持所有平台和最低配置要求。SQLite 允许您直接读写本地磁盘中的数据库,而不需要单独的服务器进程。这使得它成为学习数据库应用程序开发的一个用户友好的选择。

使用 SQLite 的另一个优点是 Python 和 PyQt 都附带了这个库,所以您不需要安装其他任何东西就可以开始使用它。

在 PyQt 中,可以通过使用 QSqlDatabase 类来创建数据库连接。这个类代表一个连接,并提供一个访问数据库的接口。要创建连接,只需调用QSqlDatabase上的 .addDatabase() 。这个静态方法将一个 SQL 驱动程序和一个可选的连接名作为参数,并且返回一个数据库连接:

QSqlDatabase.addDatabase(
    driver, connectionName=QSqlDatabase.defaultConnection
)

第一个参数driver是一个必需的参数,它保存一个字符串,该字符串包含一个受 PyQt 支持的 SQL 驱动程序的名称。第二个参数connectionName是一个可选参数,它保存一个带有连接名称的字符串。connectionName默认为QSqlDatabase.defaultConnection,通常持有字符串"qt_sql_default_connection"

如果您已经有了一个名为connectionName的连接,那么这个连接将被删除并替换为一个新的连接,而.addDatabase()会将新添加的数据库连接返回给调用者。

.addDatabase()的调用将数据库连接添加到可用连接列表中。这个列表是一个全局注册表,PyQt 在后台维护它来跟踪应用程序中可用的连接。用一个有意义的connectionName注册你的连接将允许你在一个数据库应用程序中管理几个连接

一旦创建了连接,您可能需要在其上设置几个属性。具体的属性集取决于您使用的驱动程序。一般来说,您需要设置一些属性,比如数据库名、用户名和访问数据库的密码。

以下是可用于设置数据库连接的更常用属性的 setter 方法的摘要:

方法 描述
.setDatabaseName(name) 将数据库名称设置为name,这是一个代表有效数据库名称的字符串
.setHostName(host) 将主机名设置为host,这是一个表示有效主机名的字符串
.setUserName(username) 将用户名设置为username,这是一个表示有效用户名的字符串
.setPassword(password) 将密码设置为password,它是一个代表有效密码的字符串

请注意,您作为参数传递给.setPassword()的密码是以纯文本形式存储的,以后可以通过调用.password()来检索。这是一个严重的安全风险,您应该避免在您的数据库应用程序中引入。在本教程后面的打开数据库连接一节中,您会学到一种更安全的方法。

要使用QSqlDatabase创建到 SQLite 数据库的连接,请打开一个 Python 交互会话,并键入以下代码:

>>> from PyQt5.QtSql import QSqlDatabase

>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")

>>> con
<PyQt5.QtSql.QSqlDatabase object at 0x7f0facec0c10>

>>> con.databaseName()
'contacts.sqlite'

>>> con.connectionName()
'qt_sql_default_connection'

这段代码将创建一个数据库连接对象,使用"QSQLITE"作为连接的驱动程序,使用"contacts.sqlite"作为连接的数据库名称。由于您没有将连接名传递给.addDatabase(),新创建的连接将成为您的默认连接,其名称为"qt_sql_default_connection"

对于 SQLite 数据库,数据库名称通常是文件名或包含数据库文件名的路径。您还可以为内存数据库使用特殊的名称":memory:"

处理多个连接

有些情况下,您可能需要使用到单个数据库的多个连接。例如,您可能希望使用每个用户的特定连接来记录用户与数据库的交互。

在其他情况下,您可能需要将应用程序连接到几个数据库。例如,您可能希望连接到几个远程数据库,以便收集数据来填充或更新本地数据库。

为了处理这些情况,您可以为不同的连接提供特定的名称,并通过名称引用每个连接。如果您想给数据库连接一个名称,那么将该名称作为第二个参数传递给.addDatabase():

>>> from PyQt5.QtSql import QSqlDatabase

>>> # First connection
>>> con1 = QSqlDatabase.addDatabase("QSQLITE", "con1")
>>> con1.setDatabaseName("contacts.sqlite")

>>> # Second connection
>>> con2 = QSqlDatabase.addDatabase("QSQLITE", "con2")
>>> con2.setDatabaseName("contacts.sqlite")

>>> con1
<PyQt5.QtSql.QSqlDatabase object at 0x7f367f5fbf90>
>>> con2
<PyQt5.QtSql.QSqlDatabase object at 0x7f3686dd7510>

>>> con1.databaseName()
'contacts.sqlite'
>>> con2.databaseName()
'contacts.sqlite'

>>> con1.connectionName()
'con1'
>>> con2.connectionName()
'con2'

在这里,您创建了到同一个数据库contacts.sqlite的两个不同的连接。每个连接都有自己的连接名称。您可以根据需要,在代码中随时使用连接名来获取对特定连接的引用。为此,您可以用一个连接名调用.database():

>>> from PyQt5.QtSql import QSqlDatabase

>>> db = QSqlDatabase.database("con1", open=False)

>>> db.databaseName()
'contacts.sqlite'
>>> db.connectionName()
'con1'

在这个例子中,您会看到.database()有两个参数:

  1. connectionName 保存着你需要使用的连接名。如果不传递连接名,将使用默认连接。
  2. open 保存一个布尔值,告诉.database()是否要自动打开连接。如果openTrue(默认)且连接未打开,则连接自动打开。

.database()返回值是对名为connectionName的连接对象的引用。您可以使用不同的连接名称来获取对特定连接对象的引用,然后使用它们来管理您的数据库。

Remove ads

使用不同的 SQL 驱动程序

到目前为止,您已经学习了如何使用 SQLite 驱动程序创建数据库连接。这不是 PyQt 中唯一可用的驱动程序。该库提供了一组丰富的 SQL 驱动程序,允许您根据自己的特定需求使用不同类型的数据库管理系统:

驱动程序名称 数据库管理系统
qd2 IBM Db2(版本 7.1 和更高版本)
QIBASE Borland InterBase
QMYSQL/MARIADB MySQL 或 Maria db(5.0 及以上版本)
QOCI Oracle 调用接口
QODBC 开放式数据库连接(ODBC)
QPSQL PostgreSQL(7.3 及更高版本)
QSQLITE2 SQLite 2(自第 5.14 季度起已过时)
QSQLITE sqlite3
qtd Sybase Adaptive Server(从第 4.7 季度起已过时)

驱动程序名称列保存了标识符字符串,您需要将它作为第一个参数传递给.addDatabase()以使用相关的驱动程序。与 SQLite 驱动程序不同,当您使用不同的驱动程序时,您可能需要设置几个属性,如 databaseNamehostNameuserNamepassword ,以便连接正常工作。

数据库驱动来源于 QSqlDriver 。您可以通过子类化QSqlDriver来创建自己的数据库驱动程序,但是这个主题超出了本教程的范围。如果你对创建你自己的数据库驱动感兴趣,那么查看如何编写你自己的数据库驱动以获得更多细节。

打开数据库连接

一旦有了数据库连接,就需要打开该连接才能与数据库进行交互。为此,您在连接对象上调用 .open().open()有以下两种变化:

  1. .open() 使用当前连接值打开一个数据库连接。
  2. .open(username, password) 使用提供的usernamepassword打开数据库连接。

如果连接成功,两个变量都返回True。否则,它们返回False。如果无法建立连接,那么您可以呼叫 .lastError() 来了解所发生的事情。该函数返回数据库报告的上一个错误的信息。

**注意:**正如您之前了解到的,.setPassword(password)以纯文本形式存储密码,这是一个安全风险。另一方面,.open()根本不存储密码。它在打开连接时将密码直接传递给驱动程序。之后,它会丢弃密码。所以,如果你想防止安全问题,使用.open()来管理你的密码是一个不错的选择。

下面是如何使用.open()的第一个变体打开 SQLite 数据库连接的示例:

>>> from PyQt5.QtSql import QSqlDatabase

>>> # Create the connection
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")

>>> # Open the connection
>>> con.open()
True
>>> con.isOpen()
True

在上面的例子中,首先创建一个到 SQLite 数据库的连接,并使用.open()打开该连接。由于.open()返回True,连接成功。此时,您可以使用.isOpen()来检查连接,如果连接打开,则返回True,否则返回False

**注意:**如果您在使用 SQLite 驱动程序的连接上调用.open(),而数据库文件不存在,那么将自动创建一个新的空数据库文件。

在现实世界的应用程序中,在尝试对数据进行任何操作之前,需要确保与数据库的连接是有效的。否则,您的应用程序可能会崩溃和失败。例如,如果您对试图创建数据库文件的目录没有写权限,该怎么办?您需要确保您正在处理打开连接时可能发生的任何错误。

调用.open()的一种常见方式是将其包装在一个条件语句中。这允许您处理打开连接时可能出现的错误:

>>> import sys
>>> from PyQt5.QtSql import QSqlDatabase

>>> # Create the connection
>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")

>>> # Open the connection and handle errors
>>> if not con.open():
...     print("Unable to connect to the database")
...     sys.exit(1)

将对.open()的调用包装在条件语句中,允许您处理打开连接时发生的任何错误。这样,您可以在应用程序运行之前通知您的用户任何问题。注意,应用程序以1退出状态退出,这通常用于指示程序失败。

在上面的例子中,您在交互会话中使用了.open(),所以您使用 print() 向用户显示错误消息。然而,在 GUI 应用程序中,你通常使用一个 QMessageBox 对象,而不是使用print()。使用QMessageBox,你可以创建小对话框向用户展示信息。

下面是一个示例 GUI 应用程序,它说明了处理连接错误的方法:

 1import sys
 2
 3from PyQt5.QtSql import QSqlDatabase
 4from PyQt5.QtWidgets import QApplication, QMessageBox, QLabel
 5
 6# Create the connection
 7con = QSqlDatabase.addDatabase("QSQLITE")
 8con.setDatabaseName("/home/contacts.sqlite")
 9
10# Create the application
11app = QApplication(sys.argv)
12
13# Try to open the connection and handle possible errors
14if not con.open():
15    QMessageBox.critical(
16        None,
17        "App Name - Error!",
18        "Database Error: %s" % con.lastError().databaseText(),
19    )
20    sys.exit(1)
21
22# Create the application's window
23win = QLabel("Connection Successfully Opened!")
24win.setWindowTitle("App Name")
25win.resize(200, 100)
26win.show()
27sys.exit(app.exec_())

第 14 行的if语句检查连接是否不成功。如果/home/目录不存在,或者如果您没有权限写入该目录,那么对.open()的调用就会失败,因为无法创建数据库文件。在这种情况下,执行流进入if语句代码块,并在屏幕上显示一条消息。

如果您将路径更改为可以写入的任何其他目录,那么对.open()的调用将会成功,您将会看到一个显示消息Connection Successfully Opened!的窗口,在所选目录中您还会有一个名为contacts.sqlite的新数据库文件。

请注意,您将None作为消息的父代传递,因为在显示消息时,您还没有创建窗口,所以您没有消息框的可行父代。

Remove ads

使用 PyQt 运行 SQL 查询

有了功能齐全的数据库连接,您就可以开始使用数据库了。为此,您可以使用基于字符串的 SQL 查询和 QSqlQuery 对象。QSqlQuery允许您在数据库中运行任何类型的 SQL 查询。使用QSqlQuery,可以执行数据操作语言(DML) 语句,如SELECTINSERTUPDATEDELETE,以及数据定义语言(DDL) 语句,如 CREATE TABLE 等。

QSqlQuery的构造函数有几种变体,但是在本教程中,您将了解其中的两种:

  1. QSqlQuery(query, connection) 使用基于字符串的 SQL query和数据库connection构造查询对象。如果没有指定连接,或者指定的连接无效,则使用默认的数据库连接。如果query不是空字符串,那么它将被立即执行。

  2. QSqlQuery(connection) 使用connection构造查询对象。如果connection无效,则使用默认连接。

您还可以创建QSqlQuery对象,而无需向构造函数传递任何参数。在这种情况下,查询将使用默认的数据库连接(如果有的话)。

要执行查询,需要在查询对象上调用.exec()。您可以以两种不同的方式使用.exec():

  1. .exec(query) 执行query中包含的基于字符串的 SQL 查询。如果查询成功,它返回True,否则返回False

  2. .exec() 执行先前准备好的 SQL 查询。如果查询成功,它返回True,否则返回False

注意: PyQt 也用名字.exec_()实现了QSqlQuery.exec()的变体。这些提供了与旧版本 Python 的向后兼容性,其中exec是该语言的关键字

现在您已经知道了使用QSqlQuery创建和执行 SQL 查询的基础,您已经准备好学习如何将您的知识付诸实践。

执行静态 SQL 查询

要开始使用 PyQt 创建和执行查询,您需要启动您最喜欢的代码编辑器或 IDE ,并创建一个名为queries.py的 Python 脚本。保存脚本,并向其中添加以下代码:

 1import sys
 2
 3from PyQt5.QtSql import QSqlDatabase, QSqlQuery
 4
 5# Create the connection
 6con = QSqlDatabase.addDatabase("QSQLITE")
 7con.setDatabaseName("contacts.sqlite")
 8
 9# Open the connection
10if not con.open():
11    print("Database Error: %s" % con.lastError().databaseText())
12    sys.exit(1)
13
14# Create a query and execute it right away using .exec()
15createTableQuery = QSqlQuery()
16createTableQuery.exec(
17    """
18 CREATE TABLE contacts (
19 id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
20 name VARCHAR(40) NOT NULL,
21 job VARCHAR(50),
22 email VARCHAR(40) NOT NULL
23 )
24 """
25)
26
27print(con.tables())

在这个脚本中,首先导入将要使用的模块和类。然后使用 SQLite 驱动程序使用.addDatabase()创建一个数据库连接。您将数据库名称设置为"contacts.sqlite"并打开连接。

为了创建您的第一个查询,您实例化了没有任何参数的QSqlQuery。有了查询对象后,调用.exec(),将基于字符串的 SQL 查询作为参数传递。这种查询被称为静态查询,因为它不从查询外部获取任何参数。

上面的 SQL 查询在数据库中创建了一个名为contacts的新表。该表将包含以下四列:

圆柱 内容
id 带有表的主键的整数
name 包含联系人姓名的字符串
job 包含联系人职务的字符串
email 联系人电子邮件的字符串

上面脚本的最后一行打印数据库中包含的表的列表。如果您运行脚本,那么您会注意到在您当前的目录中创建了一个名为contacts.sqlite的新数据库文件。您还会在屏幕上看到类似于['contacts', 'sqlite_sequence']的内容。这个列表包含数据库中的表名。

**注意:**基于字符串的 SQL 查询必须根据您正在查询的特定 SQL 数据库使用适当的语法。如果语法错误,那么.exec()忽略查询并返回False

对于 SQLite,查询一次只能包含一条语句。

在一个QSqlQuery对象上调用.exec()是在数据库上立即执行基于字符串的 SQL 查询的一种常见方式,但是如果您想为以后的执行预先准备好查询呢?这是下一节的主题。

执行动态查询:字符串格式化

到目前为止,您已经学习了如何在数据库上执行静态查询。静态查询是那些不接受参数的查询,所以查询按原样运行。尽管这些查询非常有用,但有时您需要创建查询来检索数据以响应某些输入参数。

在执行时接受参数的查询被称为动态查询。使用参数允许您微调查询并检索数据以响应特定的参数值。不同的值会产生不同的结果。您可以使用以下两种方法之一在查询中获取输入参数:

  1. 动态构建查询,使用字符串格式插入参数值。
  2. 使用占位符参数准备查询,然后将特定值绑定到参数。

第一种方法允许您快速创建动态查询。但是,为了安全地使用这种方法,您需要确保您的参数值来自可靠的来源。否则,你可能会面临 SQL 注入的攻击

下面是一个如何在 PyQt 中使用字符串格式创建动态查询的示例:

>>> from PyQt5.QtSql import QSqlQuery, QSqlDatabase

>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> con.open()
True

>>> name = "Linda"
>>> job = "Technical Lead"
>>> email = "linda@example.com"

>>> query = QSqlQuery()
>>> query.exec(
...     f"""INSERT INTO contacts (name, job, email)
...     VALUES ('{name}', '{job}', '{email}')"""
... )
True

在这个例子中,您使用一个 f 字符串来创建一个动态查询,方法是将特定的值插入到一个基于字符串的 SQL 查询中。最后一个查询将数据插入到您的contacts表中,该表现在包含关于Linda的数据。

**注意:**在本教程的后面,您将看到如何检索和导航存储在数据库中的数据。

注意,为了让这种动态查询工作,您需要确保要插入的值具有正确的数据类型。因此,您在 f 字符串中的占位符周围使用单引号,因为这些值需要是字符串。

Remove ads

执行动态查询:占位符参数

执行动态查询的第二种方法要求您使用带有参数占位符的模板来预先准备查询。PyQt 支持两种参数占位符样式:

  1. Oracle style 使用命名占位符,如:name:email
  2. ODBC 样式使用问号(?)作为位置占位符。

请注意,这些样式不能在同一个查询中混合使用。您可以查看绑定值的方法,以获得如何使用占位符的额外示例。

注: ODBC 代表开放式数据库连接

要在 PyQt 中创建这种动态查询,首先为每个查询参数创建一个带有占位符的模板,然后将该模板作为一个参数传递给.prepare(),它解析、编译并准备查询模板以供执行。如果模板有任何问题,比如 SQL 语法错误,那么.prepare()无法编译模板并返回False

如果准备过程成功,那么prepare()返回True。之后,您可以使用带有命名参数或位置参数的.bindValue()或带有位置参数的.addBindValue()将特定值传递给每个参数。.bindValue()有以下两种变体:

  1. .bindValue(placeholder, val)
  2. .bindValue(pos, val)

在第一个变体中,placeholder表示一个 Oracle 样式的占位符。在第二个变体中,pos表示一个从零开始的整数,带有查询中参数的位置。在这两种变体中,val保存要绑定到特定参数的值。

使用位置绑定向占位符列表添加一个值。这意味着对.addBindValue()的调用顺序决定了哪个值将被绑定到准备好的查询中的每个占位符参数。

要开始使用准备好的查询,您可以准备一个INSERT INTO SQL 语句,用一些样本数据填充您的数据库。回到您在执行静态 SQL 查询一节中创建的脚本,并在调用print()之后添加以下代码:

28# Creating a query for later execution using .prepare()
29insertDataQuery = QSqlQuery()
30insertDataQuery.prepare(
31    """
32 INSERT INTO contacts (
33 name,
34 job,
35 email
36 )
37 VALUES (?, ?, ?)
38 """
39)
40
41# Sample data
42data = [
43    ("Joe", "Senior Web Developer", "joe@example.com"),
44    ("Lara", "Project Manager", "lara@example.com"),
45    ("David", "Data Analyst", "david@example.com"),
46    ("Jane", "Senior Python Developer", "jane@example.com"),
47]
48
49# Use .addBindValue() to insert data
50for name, job, email in data:
51    insertDataQuery.addBindValue(name)
52    insertDataQuery.addBindValue(job)
53    insertDataQuery.addBindValue(email)
54    insertDataQuery.exec()

第一步是创建一个QSqlQuery对象。然后在查询对象上调用.prepare()。在这种情况下,占位符使用 ODBC 样式。您的查询将获取联系人的namejobemail的值,因此您需要三个占位符。因为id列是一个自动递增的整数,所以不需要为它提供值。

然后创建一些样本数据来填充数据库。data保存了元组的列表,每个元组包含三项:每个联系人的姓名、工作和电子邮件。

最后一步是绑定要传递给每个占位符的值,然后调用.exec()来执行查询。为此,您使用一个 for循环。循环头将data中的每个元组解包成三个独立的变量,并使用方便的名称。然后调用查询对象上的.addBindValue()将值绑定到占位符。

注意,您使用的是位置占位符,因此您调用.addBindValue()的顺序将定义每个值传递给相应占位符的顺序。

当您希望使用来自用户输入的值来定制查询时,这种创建动态查询的方法非常方便。每当您接受用户的输入来完成对数据库的查询时,您就面临着 SQL 注入的安全风险。

在 PyQt 中,组合使用.prepare().bindValue().addBindValue()可以完全保护您免受 SQL 注入攻击,所以当您接受不可信的输入来完成查询时,这是一个不错的选择。

在查询中导航记录

如果您执行一个SELECT语句,那么您的QSqlQuery对象将从数据库中的一个或多个表中检索零个或多个记录。该查询将保存包含与查询条件匹配的数据的记录。如果没有符合条件的数据,那么您的查询将为空。

QSqlQuery提供了一组导航方法,您可以使用它们在查询结果中的记录间移动:

方法 恢复
T2.next() 下一张唱片
T2.previous() 以前的记录
T2.first() 第一张唱片
T2.last() 最后一张唱片
T2.seek(index, relative=False) 位置index的记录

如果记录可用,所有这些方法都将查询对象定位在检索到的记录上。大多数方法在使用时都有特定的规则。使用这些方法,您可以在查询结果的记录中向前、向后或任意移动。因为它们都返回TrueFalse,所以您可以在 while循环中使用它们来一次导航所有记录。

这些方法与主动查询一起工作。当您成功地对一个查询运行了.exec()时,该查询是活动的,但是该查询还没有完成。一旦一个活动查询位于一个有效记录上,您就可以使用 .value(index) 从该记录中检索数据。该方法采用一个从零开始的整数index,并返回当前记录中该索引(列)处的值。

**注意:**如果您执行一个SELECT *类型的查询,那么结果中的列将不会遵循已知的顺序。当您使用.value()检索给定列的值时,这可能会导致问题,因为无法知道您是否使用了正确的列索引。

您将看到一些例子,展示如何使用一些导航方法在下面的查询中移动。但是首先,您需要创建一个到数据库的连接:

>>> from PyQt5.QtSql import QSqlDatabase, QSqlQuery

>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")
>>> con.open()
True

在这里,您创建并打开一个到contacts.sqlite的新连接。如果到目前为止您一直在学习本教程,那么这个数据库已经包含了一些示例数据。现在您可以创建一个QSqlQuery对象,并对该数据执行它:

>>> # Create and execute a query
>>> query = QSqlQuery()
>>> query.exec("SELECT name, job, email FROM contacts")
True

该查询检索存储在contacts表中的所有联系人的namejobemail的数据。由于.exec()返回了True,查询成功,现在是一个活动查询。您可以使用之前看到的任何导航方法来导航该查询中的记录。您还可以使用.value()检索记录中任何一列的数据:

>>> # First record
>>> query.first()
True

>>> # Named indices for readability
>>> name, job, email = range(3)

>>> # Retrieve data from the first record
>>> query.value(name)
'Linda'

>>> # Next record
>>> query.next()
True
>>> query.value(job)
'Senior Web Developer'

>>> # Last record
>>> query.last()
True
>>> query.value(email)
'jane@example.com'

使用导航方法,您可以在查询结果中移动。使用.value(),您可以检索给定记录中任何列的数据。

您还可以使用一个while循环和.next()遍历查询中的所有记录:

>>> query.exec()
True

>>> while query.next():
...     print(query.value(name), query.value(job), query.value(email))
...
Linda Technical Lead linda@example.com
Joe Senior Web Developer joe@example.com
...

使用.next(),您可以浏览查询结果中的所有记录。.next()的工作方式类似于 Python 中的迭代器协议。一旦您遍历了查询结果中的记录,.next()就开始返回False,直到您再次运行.exec()。对.exec()的调用从数据库中检索数据,并将查询对象的内部指针放在第一条记录之前的一个位置,因此当您调用.next()时,您将再次获得第一条记录。

您也可以使用.previous()以相反的顺序循环:

>>> while query.previous():
...     print(query.value(name), query.value(job), query.value(email))
...
Jane Senior Python Developer jane@example.com
David Data Analyst david@example.com
...

.previous()的工作与.next()相似,但是迭代是以相反的顺序进行的。换句话说,循环从查询指针的位置返回到第一条记录。

有时,您可能希望通过使用表中给定列的名称来获取标识该列的索引。为此,您可以对.record()的返回值调用.indexOf():

>>> query.first()
True

>>> # Get the index of name
>>> name = query.record().indexOf("name")

>>> query.value(name)
'Linda'

>>> # Finish the query object if unneeded
>>> query.finish()
>>> query.isActive()
False

.record()结果的.indexOf()调用返回了"name"列的索引。如果"name"不存在,那么.indexOf()返回-1。当使用列顺序未知的SELECT *语句时,这很方便。最后,如果您完成了一个查询对象,那么您可以通过调用.finish()使它不活动。这将释放与当前查询对象相关的系统内存。

Remove ads

关闭和移除数据库连接

在实践中,一些 PyQt 应用程序依赖于数据库,而另一些则不依赖。依赖于数据库的应用程序通常在创建任何窗口或图形组件之前创建并打开数据库连接,并保持连接打开,直到应用程序关闭。

另一方面,不依赖于数据库但使用数据库提供某些功能的应用程序通常只在需要时才连接到数据库。在这些情况下,您可以在使用后关闭连接,并释放与该连接相关的资源,如系统内存。

要关闭 PyQt 中的连接,可以在连接上调用 .close() 。该方法关闭连接并释放所有获取的资源。它还会使任何关联的QSqlQuery对象无效,因为没有活动连接它们就无法正常工作。

下面是如何使用.close()关闭活动数据库连接的示例:

>>> from PyQt5.QtSql import QSqlDatabase

>>> con = QSqlDatabase.addDatabase("QSQLITE")
>>> con.setDatabaseName("contacts.sqlite")

>>> con.open()
True
>>> con.isOpen()
True

>>> con.close()
>>> con.isOpen()
False

您可以在一个连接上调用.close()来关闭它并释放所有相关的资源。为了确保一个连接被关闭,你调用 .isOpen()

请注意,QSqlQuery对象在关闭其关联的连接后仍保留在内存中,因此您必须通过调用 .finish().clear() 或在关闭连接前删除QSqlQuery对象来使您的查询处于非活动状态。否则,查询对象中会遗漏剩余内存。

您可以重新打开并重用任何以前关闭的连接。这是因为.close()不会从可用连接列表中删除连接,所以它们仍然可用。

您也可以使用 .removeDatabase() 完全移除您的数据库连接。为了安全起见,首先使用.finish()完成查询,然后使用.close()关闭数据库,最后移除连接。您可以使用.removeDatabase(connectionName)从可用连接列表中删除名为connectionName的数据库连接。移除的连接不再可用于手边的应用程序。

要移除默认的数据库连接,可以对 .database() 返回的对象调用 .connectionName() ,并将结果传递给.removeDatabase():

>>> # The connection is closed but still in the list of connections
>>> QSqlDatabase.connectionNames()
['qt_sql_default_connection']

>>> # Remove the default connection
>>> QSqlDatabase.removeDatabase(QSqlDatabase.database().connectionName())

>>> # The connection is no longer in the list of connections
>>> QSqlDatabase.connectionNames()
[]

>>> # Try to open a removed connection
>>> con.open()
False

这里,对.connectionNames()的调用返回可用连接的列表。在这种情况下,您只有一个连接,这是默认连接。然后使用.removeDatabase()移除连接。

注意:在关闭和删除数据库连接之前,您需要确保使用该连接的所有内容都被删除或设置为使用不同的数据源。否则,你会有一个资源泄露

因为您需要一个连接名来使用.removeDatabase(),所以您在.database()的结果上调用.connectionName()来获得默认连接的名称。最后,您再次调用.connectionNames()来确保该连接不再在可用连接列表中。试图打开一个删除的连接将返回False,因为该连接不再存在。

用 PyQt 显示和编辑数据

在使用数据库的 GUI 应用程序中,一个常见的需求是能够使用不同的小部件从数据库中加载、显示和编辑数据。表格列表控件是 GUI 中管理数据的常用控件。

PyQt 为管理数据提供了两种不同的小部件:

  1. 标准小部件包括用于存储数据的内部容器。
  2. 视图小部件不维护内部数据容器,而是使用模型来访问数据。

对于管理小型数据库的小型 GUI 应用程序,您可以使用第一种方法。当您构建管理大型数据库的复杂 GUI 应用程序时,第二种方法非常方便。

第二种方法利用了 PyQt 的模型视图编程。使用这种方法,一方面有表示视图(如表、列表和树)的小部件,另一方面有与数据通信的模型类。

Remove ads

理解 PyQt 的模型-视图架构

模型-视图-控制器(MVC) 设计模式是一种通用软件模式,旨在将应用程序的代码分为三个通用层,每个层都有不同的角色。

模型负责应用程序的业务逻辑,视图提供屏幕显示,而控制器连接模型和视图以使应用程序工作。

Qt 提供了 MVC 的定制变体。他们称之为模型-视图架构,它也适用于 PyQt。该模式还将逻辑分为三个部分:

  1. 模型与数据通信并访问数据。它们还定义了视图和代理用来访问数据的接口。所有型号均基于 QAbstractItemModel 。一些常用的模型包括QStandardItemModelQFileSystemModel和 SQL 相关的模型。

  2. 视图负责向用户显示数据。它们还具有与 MVC 模式中的控制器相似的功能。所有视图均基于 QAbstractItemView 。一些常用的视图有QListViewQTableViewQTreeView

  3. 委托绘制视图项目并提供用于修改项目的编辑器部件。如果某个项目被修改,它们还会与模型进行通信。基类是 QAbstractItemDelegate

将类分成这三个组件意味着模型上的变化将自动反映在相关的视图或小部件上,并且通过委托对视图或小部件的变化将自动更新底层模型。

此外,您可以在不同的视图中显示相同的数据,而不需要多个模型。

使用标准部件类

PyQt 提供了一堆标准的小部件,用于在 GUI 应用程序中显示和编辑数据。这些标准小部件提供了表格、树和列表等视图。它们还为存储数据提供了一个内部容器,并为编辑数据提供了方便的委托。所有这些特性都归入一个类。

以下是其中的三个标准类:

标准等级 显示
T2QListWidget 项目清单
T2QTreeWidget 项目的层次树
T2QTableWidget 项目表

在显示和编辑数据方面,可以说是最受欢迎的小部件。它创建了一个由 QTableWidgetItem 对象组成的 2D 数组。每一项都以字符串形式保存一个单独的值。所有这些值都显示并组织在一个由行和列组成的表格中。

您至少可以对一个QTableWidget对象执行以下操作:

下面是一个示例应用程序,展示了如何使用一个QTableWidget对象在 GUI 中显示数据。应用程序使用您在前面几节中创建和填充的数据库,因此如果您想要运行它,那么您需要将代码保存到您拥有contacts.sqlite数据库的同一个目录中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果您双击表格的任何单元格,那么您将能够编辑单元格的内容。但是,您的更改不会保存到数据库中。

下面是您的应用程序的代码:

 1import sys
 2
 3from PyQt5.QtSql import QSqlDatabase, QSqlQuery
 4from PyQt5.QtWidgets import (
 5    QApplication,
 6    QMainWindow,
 7    QMessageBox,
 8    QTableWidget,
 9    QTableWidgetItem,
10)
11
12class Contacts(QMainWindow):
13    def __init__(self, parent=None):
14        super().__init__(parent)
15        self.setWindowTitle("QTableView Example")
16        self.resize(450, 250)
17        # Set up the view and load the data
18        self.view = QTableWidget()
19        self.view.setColumnCount(4)
20        self.view.setHorizontalHeaderLabels(["ID", "Name", "Job", "Email"])
21        query = QSqlQuery("SELECT id, name, job, email FROM contacts")
22        while query.next():
23            rows = self.view.rowCount()
24            self.view.setRowCount(rows + 1)
25            self.view.setItem(rows, 0, QTableWidgetItem(str(query.value(0))))
26            self.view.setItem(rows, 1, QTableWidgetItem(query.value(1)))
27            self.view.setItem(rows, 2, QTableWidgetItem(query.value(2)))
28            self.view.setItem(rows, 3, QTableWidgetItem(query.value(3)))
29        self.view.resizeColumnsToContents()
30        self.setCentralWidget(self.view)
31
32def createConnection():
33    con = QSqlDatabase.addDatabase("QSQLITE")
34    con.setDatabaseName("contacts.sqlite")
35    if not con.open():
36        QMessageBox.critical(
37            None,
38            "QTableView Example - Error!",
39            "Database Error: %s" % con.lastError().databaseText(),
40        )
41        return False
42    return True
43
44app = QApplication(sys.argv)
45if not createConnection():
46    sys.exit(1)
47win = Contacts()
48win.show()
49sys.exit(app.exec_())

以下是本例中发生的情况:

  • 第 18 到 20 行创建一个QTableWidget对象,将列数设置为4,并为每一列的标题设置用户友好的标签。
  • 第 21 行在数据库上创建并执行一个SELECT SQL 查询,以获取contacts表中的所有数据。
  • 第 22 行开始一个while循环,使用.next()导航查询结果中的记录。
  • 第 24 行使用.setRowCount()通过1增加表格中的行数。
  • 第 25 到 28 行使用.setItem()将数据项添加到您的表格中。注意,由于id列中的值是整数,您需要将它们转换成字符串,以便能够将它们存储在QTableWidgetItem对象中。

.setItem()需要三个参数:

  1. row 保存一个从零开始的整数,表示表中给定行的索引。
  2. column 保存一个从零开始的整数,表示表中给定列的索引。
  3. item 保存需要放置在表格中给定单元格的QTableWidgetItem对象。

最后,在视图上调用.resizeColumnsToContents()来调整列的大小以适应它们的内容,并提供更好的数据呈现。

使用标准小部件显示和编辑数据库表可能会成为一项具有挑战性的任务。这是因为您将拥有相同数据的两份副本。换句话说,您将在两个位置拥有数据的副本:

  1. 在小部件外部,在您的数据库中
  2. 在小部件内部,在小部件的内部容器中

您负责手动同步数据的两个副本,这可能是一个令人讨厌且容易出错的操作。幸运的是,您可以使用 PyQt 的模型视图架构来避免这些问题,您将在下一节中看到。

Remove ads

使用视图和模型类

PyQt 的模型视图类消除了使用标准小部件类构建数据库应用程序时可能出现的数据复制和同步问题。模型-视图架构允许您使用多个视图来显示相同的数据,因为您可以将一个模型传递给多个视图。

模型类提供了一个应用程序编程接口(API) ,您可以使用它来操作数据。视图类提供了方便的委托对象,您可以使用这些对象直接编辑视图中的数据。要将一个视图与一个给定的模块连接起来,您需要在视图对象上调用.setModel()

PyQt 提供了一组支持模型-视图架构的视图类:

查看类别 显示
T2QListView 直接从模型类中获取值的项目列表
T2QTreeView 直接从模型类中获取值的分层项目树
T2QTableView 直接从模型类中获取值的项目表

您可以使用这些视图类和模型类来创建您的数据库应用程序。这将使您的应用程序更加健壮,编码速度更快,更不容易出错。

下面是 PyQt 为处理 SQL 数据库提供的一些模型类:

模型类 描述
T2QSqlQueryModel 用于 SQL 查询的只读数据模型
T2QSqlTableModel 用于在单个表中读写记录的可编辑数据模型
T2QSqlRelationalTableModel 用于在关系表中读写记录的可编辑数据模型

一旦您将这些模型之一连接到物理数据库表或查询,您就可以使用它们来填充您的视图。视图提供了委托对象,允许您直接在视图中修改数据。连接到视图的模型将更新数据库中的数据,以反映视图中的任何更改。请注意,您不必手动更新数据库中的数据。模特会帮你做到的。

下面的例子展示了如何使用 PyQt 的模型-视图架构将一个QTableView对象和一个QSqlTableModel对象结合起来构建一个数据库应用程序:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

要编辑表格单元格中的数据,您可以双击该单元格。一个方便的 delegate 小部件将显示在单元格中,允许您编辑其内容。然后你可以点击 Enter 保存修改。

自动处理和保存数据变化的能力是使用 PyQt 的模型视图类的最重要的优势之一。模型-视图架构将提高您的生产率,并减少您自己编写数据操作代码时可能出现的错误。

下面是创建应用程序的代码:

 1import sys
 2
 3from PyQt5.QtCore import Qt
 4from PyQt5.QtSql import QSqlDatabase, QSqlTableModel
 5from PyQt5.QtWidgets import (
 6    QApplication,
 7    QMainWindow,
 8    QMessageBox,
 9    QTableView,
10)
11
12class Contacts(QMainWindow):
13    def __init__(self, parent=None):
14        super().__init__(parent)
15        self.setWindowTitle("QTableView Example")
16        self.resize(415, 200)
17        # Set up the model
18        self.model = QSqlTableModel(self)
19        self.model.setTable("contacts")
20        self.model.setEditStrategy(QSqlTableModel.OnFieldChange)
21        self.model.setHeaderData(0, Qt.Horizontal, "ID")
22        self.model.setHeaderData(1, Qt.Horizontal, "Name")
23        self.model.setHeaderData(2, Qt.Horizontal, "Job")
24        self.model.setHeaderData(3, Qt.Horizontal, "Email")
25        self.model.select()
26        # Set up the view
27        self.view = QTableView()
28        self.view.setModel(self.model)
29        self.view.resizeColumnsToContents()
30        self.setCentralWidget(self.view)
31
32def createConnection():
33    con = QSqlDatabase.addDatabase("QSQLITE")
34    con.setDatabaseName("contacts.sqlite")
35    if not con.open():
36        QMessageBox.critical(
37            None,
38            "QTableView Example - Error!",
39            "Database Error: %s" % con.lastError().databaseText(),
40        )
41        return False
42    return True
43
44app = QApplication(sys.argv)
45if not createConnection():
46    sys.exit(1)
47win = Contacts()
48win.show()
49sys.exit(app.exec_())

下面是这段代码中发生的情况:

  • 第 18 行创建一个可编辑的QSqlTableModel对象。
  • 第 19 行使用.setTable()将您的模型与数据库中的contacts表连接起来。
  • 第 20 行将模型的编辑策略设置为OnFieldChange。如果用户直接在视图中修改任何数据,该策略允许模型自动更新数据库中的数据。
  • 第 21 到 24 行使用.setHeaderData()为模型的水平标题设置一些用户友好的标签。
  • 第 25 行从您的数据库加载数据,并通过调用.select()填充模型。
  • 第 27 行创建表格视图对象来显示模型中包含的数据。
  • 第 28 行通过使用数据模型作为参数调用视图上的.setModel()来连接视图和模型。
  • 第 29 行调用视图对象上的.resizeColumnsToContents()来调整表格以适应其内容。

就是这样!现在,您拥有了一个全功能的数据库应用程序。

在 PyQt 中使用 SQL 数据库:最佳实践

说到有效地使用 PyQt 的 SQL 支持,有一些您可能想在应用程序中使用的最佳实践:

  • 支持 PyQt 的 SQL 支持而不是 Python 标准库或第三方库,以利用这些类与 PyQt 的其余类和基础设施的自然集成,主要是与模型-视图架构的集成。

  • 使用之前准备的带有参数占位符的动态查询,并使用.addBindValue().bindValue()将值绑定到这些参数。这将有助于防止 SQL 注入袭击。

  • 处理打开数据库连接时可能发生的错误,以避免意外行为和应用程序崩溃。

  • 关闭并移除不需要的数据库连接和查询以释放任何获取的系统资源。

  • 尽量少用SELECT *查询,避免用.value()检索数据时出现问题。

  • 把你的密码传给.open()而不是.setPassword() 以避免危及你的安全。

  • 利用 PyQt 的模型-视图架构及其与 PyQt 的 SQL 支持的集成,使您的应用程序更加健壮。

这个列表并不完整,但是它将帮助您在开发数据库应用程序时更好地利用 PyQt 的 SQL 支持。

Remove ads

结论

对于任何创建 PyQt GUI 应用程序并需要将它们连接到数据库的 Python 开发人员来说,使用 PyQt 的内置支持来处理 SQL 数据库是一项重要的技能。PyQt 为管理 SQL 数据库提供了一组一致的类。

这些类与 PyQt 的模型视图架构完全集成,允许您开发能够以用户友好的方式管理数据库的 GUI 应用程序。

在本教程中,您已经学会了如何:

  • 使用 PyQt 的 SQL 支持来连接数据库
  • 使用 PyQt 在数据库上执行 SQL 查询
  • 使用 PyQt 的模型-视图架构构建数据库应用程序
  • 使用 PyQt 小部件显示和编辑数据库中的数据

有了这些知识,您可以在创建重要的数据库应用程序时提高生产率,并使您的 GUI 应用程序更加健壮。********

Python 和 PyQt:构建 GUI 桌面计算器

原文:https://realpython.com/python-pyqt-gui-calculator/

尽管网络和移动应用程序似乎已经占领了软件开发市场,但传统的图形用户界面(GUI) 桌面应用程序仍然有需求。如果您对用 Python 构建这些类型的应用程序感兴趣,那么您会发现有各种各样的库可供选择。他们包括 TkinterwxPythonPyQtPySide 以及其他一些人。

在本教程中,您将学习用 Python 和 PyQt 构建 GUI 桌面应用程序的基础。

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

  • 用 Python 和 PyQt 创建图形用户界面
  • 将应用程序 GUI 上的用户事件应用程序的逻辑联系起来
  • 使用适当的项目布局组织一个 PyQt 应用程序
  • 用 PyQt 创建一个功能齐全的 GUI 应用程序

在本教程中,您将使用 Python 和 PyQt 创建一个计算器应用程序。这个简短的项目将帮助您掌握基础知识,并让您开始使用这个 GUI 库。

您可以通过点击下面的链接下载该项目的源代码和本教程中的所有示例:

下载代码: 点击此处下载代码,您将在本教程中使用用 PyQt 在 Python 中构建一个计算器。

了解 PyQt

PyQt 是对 Qt 的 Python 绑定,Qt 是一组 C++ 库和开发工具,为图形用户界面(GUI)提供平台无关的抽象。Qt 还提供了联网工具、线程正则表达式SQL 数据库SVGOpenGLXML 以及其他许多强大的功能。

PyQt 由河岸计算有限公司开发,最新版本有:

  1. PyQt5 :仅针对 Qt 5.x 构建的版本
  2. PyQt6 :仅针对 Qt 6.x 构建的版本

在本教程中,您将使用 PyQt6,因为这个版本是库的未来。从现在开始,一定要把任何提到 PyQt 的地方都看作是对 PyQt6 的引用。

**注意:**如果您想更深入地了解这两个版本的库之间的差异,那么请查看关于该主题的 PyQt6 文档

PyQt6 基于 Qt v6 。因此,它提供了用于 GUI 创建的和工具、 XML 处理、网络通信、正则表达式、线程、SQL 数据库、网页浏览以及 Qt 中可用的其他技术。PyQt6 在一组 Python 模块中实现了许多 Qt 类的绑定,这些模块被组织在名为PyQt6的顶级 Python 中。要使 PyQt6 工作,您需要 Python 3.6.1 或更高版本。

PyQt6 兼容 Windows、Unix、Linux、macOS、iOS 和 Android。如果您正在寻找一个 GUI 框架来开发在每个平台上都具有本机外观的多平台应用程序,这是一个很有吸引力的特性。

PyQt6 有两种许可证:

  1. 河岸商业许可证
  2. 通用公共许可证(GPL),第三版

您的 PyQt6 许可证必须与 Qt 许可证兼容。如果您使用 GPL 许可证,那么您的代码也必须使用 GPL 兼容的许可证。如果您想使用 PyQt6 创建商业应用程序,那么您的安装需要一个商业许可证。

注意:Qt 公司已经为 Qt 库开发并维护了自己的 Python 绑定。Python 库被称为 Python 的Qt,是 Python 的官方 Qt。它的 Python 包叫做 PySide。

PyQt 和 PySide 都是建立在 Qt 之上的。它们的 API 非常相似,因为它们都反映了 Qt API。这就是为什么将 PyQt 代码移植到 PySide 可以像更新一些导入一样简单。如果你学会了其中的一种,那么你就可以不费吹灰之力地学会另一种。如果您想更深入地了解这两个库之间的差异,那么您可以查看一下 PyQt6 与 PySide6

如果你需要更多关于 PyQt6 许可的信息,那么查看项目官方文档上的许可常见问题页面。

Remove ads

安装 PyQt

在您的系统或开发环境中安装 PyQt 有几种选择。推荐的选项是使用二元。Wheels 是从 Python 包索引 PyPI 安装 Python 包的标准方式。

在任何情况下,您都需要考虑 PyQt6 的轮子只适用于 Python 3.6.1 和更高版本。有适用于 Linux、macOS 和 Windows (64 位)的轮子。

所有这些轮子都包含相应 Qt 库的副本,所以您不需要单独安装它们。

另一个安装选项是从源代码构建 PyQt。这可能有点复杂,所以如果可能的话,您可能想要避免它。如果你真的需要从源代码开始构建,那么看看这个库的文档在这些情况下推荐了什么。

或者,你可以选择使用包管理器,比如 Linux 上的 APT 或者 macOS 上的 Homebrew 来安装 PyQt6。在接下来的几节中,您将了解在不同平台上从不同来源安装 PyQt6 的一些选项。

虚拟环境安装用pip

大多数时候,你应该创建一个 Python 虚拟环境以隔离的方式安装 PyQt6。要创建一个虚拟环境并在其中安装 PyQt6,请在命令行上运行以下命令:

PS> python -m venv venv
PS> venv\Scripts\activate
(venv) PS> python -m pip install pyqt6
$ python -m venv venv
$ source venv/bin/activate
(venv) $ python -m pip install pyqt6

在这里,首先使用标准库中的venv模块创建一个虚拟环境。然后激活它,最后使用pip在其中安装 PyQt6。请注意,您必须拥有 Python 3.6.1 或更高版本,install 命令才能正常工作。

pip 进行全系统安装

您很少需要在您的系统 Python 环境中直接安装 PyQt。如果您需要进行这种安装,请在不激活任何虚拟环境的情况下,在命令行或终端窗口中运行以下命令:

$ python -m pip install pyqt6

使用这个命令,您将直接在您的系统 Python 环境中安装 PyQt6。安装完成后,您可以立即开始使用该库。根据您的操作系统,您可能需要 root 或管理员权限才能进行此安装。

尽管这是一种快速安装 PyQt6 并立即开始使用的方法,但这并不是推荐的方法。推荐的方法是使用 Python 虚拟环境,正如您在上一节中所学的。

特定于平台的安装

一些 Linux 发行版在它们的存储库中包含了 PyQt6 的二进制包。如果这是您的情况,那么您可以使用发行版的软件包管理器来安装这个库。例如,在 Ubuntu 上,您可以使用以下命令:

$ sudo apt install python3-pyqt6

使用这个命令,您将在基本系统中安装 PyQt6 及其所有依赖项,这样您就可以在任何 GUI 项目中使用这个库。注意,需要 root 权限,您可以在这里用sudo命令调用它。

如果你是 macOS 用户,那么你可以使用 Homebrew 包管理器来安装 PyQt6。为此,请打开终端并运行以下命令:

$ brew install pyqt6

运行该命令后,PyQt6 将安装在您的 Homebrew Python 环境中,并且可以使用了。

如果你在 Linux 或 macOS 上使用包管理器,那么你有可能得不到最新版本的 PyQt6。如果你想确保你有最新的版本,安装会更好。

Remove ads

创建您的第一个 PyQt 应用程序

现在您已经有了一个工作的 PyQt 安装,您已经准备好创建您的第一个 GUI 应用程序了。您将使用 Python 和 PyQt 创建一个Hello, World!应用程序。以下是您将遵循的步骤:

  1. PyQt6.QtWidgets 导入 QApplication 和所有需要的小部件。
  2. 创建一个QApplication的实例。
  3. 创建应用程序的 GUI。
  4. 显示你的应用程序的图形用户界面。
  5. 运行应用程序的事件循环,或主循环。

您可以通过单击下面的链接下载您将在本节中编写的示例的源代码:

下载代码: 点击此处下载代码,您将在本教程中使用用 PyQt 在 Python 中构建一个计算器。

首先,在当前工作目录下创建一个名为hello.py的新文件:

# hello.py

"""Simple Hello, World example with PyQt6."""

import sys

# 1\. Import QApplication and all the required widgets from PyQt6.QtWidgets import QApplication, QLabel, QWidget

首先,您导入 sys ,这将允许您通过 exit() 函数处理应用程序的终止和退出状态。然后你从 QtWidgets 导入QApplicationQLabelQWidget ,这是PyQt6包的一部分。有了这些导入,你就完成了第一步。

要完成第二步,您只需要创建一个QApplication的实例。就像创建任何 Python 类的实例一样:

# hello.py
# ...

# 2\. Create an instance of QApplication app = QApplication([])

在这行代码中,您创建了QApplication的实例。您应该在 PyQt 中创建任何 GUI 对象之前创建您的app实例。

在内部,QApplication类处理命令行参数。这就是为什么你需要向类构造器传递一个命令行参数列表。在这个例子中,您使用一个空列表,因为您的应用程序不会处理任何命令行参数。

**注意:**你会经常发现开发人员将 sys.argv 传递给QApplication的构造函数。该对象包含传递给 Python 脚本的命令行参数列表。如果您的应用程序需要接受命令行参数,那么您应该使用sys.argv来处理它们。否则,你可以只使用一个空列表,就像你在上面的例子中所做的那样。

第三步包括创建应用程序的 GUI。在这个例子中,您的 GUI 将基于QWidget类,它是 PyQt 中所有用户界面对象的基类。

以下是创建应用程序 GUI 的方法:

# hello.py
# ...

# 3\. Create your application's GUI window = QWidget()
window.setWindowTitle("PyQt App")
window.setGeometry(100, 100, 280, 80)
helloMsg = QLabel("<h1>Hello, World!</h1>", parent=window)
helloMsg.move(60, 15)

在这段代码中,windowQWidget的一个实例,它提供了创建应用程序窗口或表单所需的所有特性。顾名思义,.setWindowTitle()在你的应用程序中设置窗口的标题。在本例中,应用程序的窗口将显示PyQt App作为其标题。

注意:更准确的说,这一步需要你创建 app 的顶层或者主窗口。术语应用程序的 GUI 有点通用。通常,应用程序的 GUI 由多个窗口组成。

您可以使用.setGeometry()来定义窗口的大小和屏幕位置。前两个参数是窗口将要放置的屏幕坐标xy。第三和第四个参数是窗口的widthheight

每个 GUI 应用程序都需要小部件,或者构成应用程序 GUI 的图形组件。在这个例子中,您使用一个QLabel小部件helloMsg,在您的应用程序窗口上显示消息Hello, World!

QLabel对象可以显示 HTML 格式的文本,因此您可以使用 HTML 元素"<h1>Hello, World!</h1>"来提供所需的文本作为h1标题。最后,使用.move()helloMsg放置在应用程序窗口的坐标(60, 15)处。

**注意:**在 PyQt 中,您可以使用任何小部件——QWidget的子类——作为顶层窗口。唯一的条件是目标小部件不能有parent小部件。当您使用一个小部件作为顶层窗口时,PyQt 会自动为它提供一个标题栏,并把它变成一个普通窗口。

小部件之间的父子关系有两个互补的目的。没有parent的微件被认为是主窗口或顶层窗口。相比之下,带有显式parent的小部件是一个子小部件,它显示在其父部件中。

这种关系也被称为所有权,父母拥有他们的孩子。PyQt 所有权模型确保如果您删除一个父部件,比如您的顶层窗口,那么它的所有子部件也将被自动删除。

为了避免内存泄漏,你应该确保任何QWidget对象都有一个父对象,除了你的顶层窗口。

您已经完成了第三步,因此可以继续最后两步,让您的 PyQt GUI 应用程序准备好运行:

# hello.py
# ...

# 4\. Show your application's GUI window.show()

# 5\. Run your application's event loop sys.exit(app.exec())

在这个代码片段中,您在window上调用.show()。对.show()的调用安排了一个绘制事件,这是一个绘制组成 GUI 的小部件的请求。然后,该事件被添加到应用程序的事件队列中。在后面的部分中,您将了解到更多关于 PyQt 事件循环的信息。

最后,通过调用.exec()启动应用程序的事件循环。对.exec()的调用被包装在对sys.exit()的调用中,这允许您在应用程序终止时干净地退出 Python 并释放内存资源。

您可以使用以下命令运行您的第一个 PyQt 应用程序:

$ python hello.py

当您运行这个脚本时,您会看到一个类似如下的窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

您的应用程序显示一个基于QWidget的窗口。窗口显示Hello, World!消息。为了显示消息,它使用了一个QLabel小部件。至此,您已经使用 PyQt 和 Python 编写了第一个 GUI 桌面应用程序!是不是很酷?

Remove ads

考虑代码风格

如果您检查前一节中的示例 GUI 应用程序的代码,那么您会注意到 PyQt 的 API 没有遵循 PEP 8 的编码风格和命名约定。PyQt 是围绕 Qt 构建的,Qt 是用 C++编写的,对函数、方法和变量使用了 camel case 命名风格。也就是说,当您开始编写 PyQt 项目时,您需要决定使用哪种命名风格。

在这方面,PEP 8 指出:

新的模块和包(包括第三方框架)应该按照这些标准编写,但是如果现有的库有不同的风格,内部一致性是首选的。(来源)

另外,Python 的禅说:

…实用性胜过纯粹性。(来源)

如果您想编写一致的 PyQt 相关代码,那么您应该坚持框架的编码风格。在本教程中,为了保持一致性,您将遵循 PyQt 编码风格。您将使用 camel case 而不是通常的 Python snake case

学习 PyQt 的基础知识

如果您想熟练地使用这个库来开发您的 GUI 应用程序,您需要掌握 PyQt 的基本组件。这些组件包括:

  • 小工具
  • 布局经理
  • 对话
  • 主窗口
  • 应用程序
  • 事件循环
  • 信号和插槽

这些元素是任何 PyQt GUI 应用程序的构建块。它们中的大多数被表示为 Python 类,位于 PyQt6.QtWidgets 模块中。这些因素极其重要。在接下来的几节中,您将了解到更多关于它们的内容。

小部件

小部件是矩形的图形组件,可以放在应用程序的窗口上来构建 GUI。小部件有几个属性和方法,允许您调整它们的外观和行为。他们也可以在屏幕上画出自己的肖像。

小部件还检测来自用户、窗口系统和其他来源的鼠标点击、按键和其他事件。每当一个小部件捕获到一个事件,它就会发出一个信号来宣布它的状态改变。PyQt 拥有丰富的现代小部件集合。每个部件都有不同的用途。

一些最常见和最有用的 PyQt 小部件是:

  • 小跟班
  • 标签
  • 线条编辑
  • 组合框
  • 单选按钮

首先是按钮。你可以通过实例化 QPushButton 来创建一个按钮,这个类提供了一个经典的命令按钮。典型的按钮有OkCancelApplyYesNoClose。下面是它们在 Linux 系统上的样子:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

像这样的按钮可能是任何 GUI 中最常用的小部件。当有人点击它们时,你的应用程序会命令电脑执行操作。这就是当用户点击一个按钮时你如何执行计算。

接下来是标签,你可以用QLabel来创建。标签可让您将有用的信息显示为文本或图像:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你将使用这样的标签来解释如何使用你的应用程序的图形用户界面。您可以通过多种方式调整标签的外观。如前所述,标签甚至可以接受 HTML 格式的文本。您还可以使用标签来指定键盘快捷键,以便将光标焦点移动到 GUI 上的给定小部件。

另一个常见的小部件是行编辑,也称为输入框。这个小部件允许您输入单行文本。您可以使用 QLineEdit 类创建线条编辑。当您需要以纯文本形式获取用户输入时,行编辑非常有用。

以下是 Linux 系统上的线条编辑效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

像这样的线编辑自动提供基本的编辑操作,如复制、粘贴、撤消、重做、拖放等。在上图中,您还可以看到第一行中的对象显示了占位符文本,以通知用户需要哪种输入。

组合框是 GUI 应用程序中另一种基本的窗口小部件。您可以通过实例化 QComboBox 来创建它们。组合框将以一种占用最小屏幕空间的方式向用户呈现一个下拉选项列表。

下面是一个组合框示例,它提供了流行编程语言的下拉列表:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个组合框是只读,这意味着用户可以从几个选项中选择一个,但是不能添加自己的选项。组合框也可以编辑,允许用户动态添加新选项。组合框也可以包含像素映射字符串,或者两者都包含。

您将了解的最后一个小部件是单选按钮,您可以使用 QRadioButton 创建它。一个QRadioButton对象是一个选项按钮,你可以点击它来打开。当您需要用户从众多选项中选择一个时,单选按钮非常有用。单选按钮中的所有选项同时出现在屏幕上:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这个单选按钮组中,一次只能选中一个按钮。如果用户选择另一个单选按钮,那么先前选择的按钮将自动关闭。

PyQt 收集了大量的小部件。在撰写本文时,有超过四十个可供您用来创建应用程序的 GUI。在这里,您只研究了一个小样本。然而,这足以向您展示 PyQt 的强大和灵活性。在下一节中,您将学习如何布置不同的小部件,以便为您的应用程序构建现代化的全功能 GUI。

Remove ads

布局管理器

现在您已经了解了小部件以及如何使用它们来构建 GUI,您需要知道如何安排一组小部件,以便您的 GUI 既连贯又实用。在 PyQt 中,您会发现一些在表单或窗口上布置小部件的技术。例如,您可以使用.resize().move()方法给小部件绝对的大小和位置。

然而,这种技术也有一些缺点。你必须:

  • 做许多手动计算,以确定每个部件的正确大小和位置
  • 做额外的计算来响应窗口调整事件
  • 当窗口的布局以任何方式改变时,重做您的大部分计算

另一种技术涉及使用.resizeEvent()来动态计算小部件的大小和位置。在这种情况下,您会遇到与前一种技术类似的问题。

最有效和推荐的技术是使用 PyQt 的布局管理器。它们将提高您的生产率,减少错误的风险,并提高代码的可维护性。

布局管理器是允许你在应用程序的窗口或表单上调整小部件大小和位置的类。它们会自动调整事件和 GUI 变化的大小,控制所有子部件的大小和位置。

**注意:**如果你开发国际化应用程序,那么你可能会看到翻译文本在句子中间被截掉。当目标自然语言比原始语言更冗长时,这种情况很可能发生。布局管理器可以根据可用空间自动调整小部件的大小,从而帮助您避免这种常见问题。然而,对于特别冗长的自然语言,这个特性有时会失效。

PyQt 提供了四个基本的布局管理器类:

  1. T2QHBoxLayout
  2. T2QVBoxLayout
  3. T2QGridLayout
  4. T2QFormLayout

第一个布局管理器类 QHBoxLayout ,从左到右水平排列小部件,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在水平布局中,小部件将从左侧开始一个接一个地出现。下面的代码示例展示了如何使用QHBoxLayout来水平排列三个按钮:

 1# h_layout.py
 2
 3"""Horizontal layout example."""
 4
 5import sys
 6
 7from PyQt6.QtWidgets import (
 8    QApplication,
 9    QHBoxLayout,
10    QPushButton,
11    QWidget,
12)
13
14app = QApplication([])
15window = QWidget()
16window.setWindowTitle("QHBoxLayout")
17
18layout = QHBoxLayout()
19layout.addWidget(QPushButton("Left"))
20layout.addWidget(QPushButton("Center"))
21layout.addWidget(QPushButton("Right"))
22window.setLayout(layout)
23
24window.show()
25sys.exit(app.exec())

以下是此示例如何创建按钮的水平布局:

  • 第 18 行创建了一个名为layoutQHBoxLayout对象。
  • 第 19 行到第 21 行通过调用.addWidget()方法给layout添加三个按钮。
  • 第 22 行.setLayout()设置layout为你的窗口布局。

当您从命令行运行python h_layout.py时,您将得到以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上图显示了水平排列的三个按钮。按钮从左到右显示的顺序与您在代码中添加它们的顺序相同。

下一个布局管理器类是 QVBoxLayout ,它从上到下垂直排列小部件,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

每个新的小部件都会出现在前一个小部件的下方。这种布局允许你构建垂直布局,在你的 GUI 上从上到下组织你的部件。

下面是如何创建一个包含三个按钮的QVBoxLayout对象:

 1# v_layout.py
 2
 3"""Vertical layout example."""
 4
 5import sys
 6
 7from PyQt6.QtWidgets import (
 8    QApplication,
 9    QPushButton,
10    QVBoxLayout,
11    QWidget,
12)
13
14app = QApplication([])
15window = QWidget()
16window.setWindowTitle("QVBoxLayout")
17
18layout = QVBoxLayout()
19layout.addWidget(QPushButton("Top"))
20layout.addWidget(QPushButton("Center"))
21layout.addWidget(QPushButton("Bottom"))
22window.setLayout(layout)
23
24window.show()
25sys.exit(app.exec())

在第 18 行,您创建了一个名为layoutQVBoxLayout实例。在接下来的三行中,您将向layout添加三个按钮。最后,通过第 22 行的.setLayout()方法,使用layout对象在垂直布局中排列小部件。

当您运行这个示例应用程序时,您将看到一个类似如下的窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此图显示了垂直排列的三个按钮,一个在另一个的下面。按钮的显示顺序与您将它们添加到代码中的顺序相同,从上到下。

您列表中的第三个布局管理器是 QGridLayout 。这个类在一个由行和列组成的网格中排列小部件。每个小部件在网格上都有一个相对位置。您可以用一对像(row, column)这样的坐标来定义小部件的位置。每个坐标必须是整数数字。这些坐标对定义了给定小部件将占据网格上的哪个单元。

网格布局如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

QGridLayout获取可用空间,将其分成rowscolumns,并将每个子部件放入自己的单元格中。

下面是如何在 GUI 中创建网格布局的方法:

 1# g_layout.py
 2
 3"""Grid layout example."""
 4
 5import sys
 6
 7from PyQt6.QtWidgets import (
 8    QApplication,
 9    QGridLayout,
10    QPushButton,
11    QWidget,
12)
13
14app = QApplication([])
15window = QWidget()
16window.setWindowTitle("QGridLayout")
17
18layout = QGridLayout()
19layout.addWidget(QPushButton("Button (0, 0)"), 0, 0)
20layout.addWidget(QPushButton("Button (0, 1)"), 0, 1)
21layout.addWidget(QPushButton("Button (0, 2)"), 0, 2)
22layout.addWidget(QPushButton("Button (1, 0)"), 1, 0)
23layout.addWidget(QPushButton("Button (1, 1)"), 1, 1)
24layout.addWidget(QPushButton("Button (1, 2)"), 1, 2)
25layout.addWidget(QPushButton("Button (2, 0)"), 2, 0)
26layout.addWidget(
27    QPushButton("Button (2, 1) + 2 Columns Span"), 2, 1, 1, 2
28)
29window.setLayout(layout)
30
31window.show()
32sys.exit(app.exec())

在这个例子中,您创建了一个应用程序,它使用一个QGridLayout对象来组织屏幕上的小部件。注意,在这种情况下,传递给.addWidget()的第二个和第三个参数是定义每个小部件在网格上的位置的整数。

在第 26 到 28 行,您向.addWidget()传递了另外两个参数。这些参数是rowSpancolumnSpan,它们是传递给函数的第四个和第五个参数。您可以使用它们让一个小部件占据多行或多列,就像您在示例中所做的那样。

如果您从命令行运行这段代码,您将会看到一个类似如下的窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这个图中,您可以看到您的小部件排列在一个由行和列组成的网格中。最后一个小部件占据了两列,正如您在第 26 到 28 行中指定的那样。

您将了解的最后一个布局管理器是 QFormLayout 。这个类在两列布局中排列小部件。第一列通常在标签中显示消息。第二列通常包含类似于QLineEditQComboBoxQSpinBox等小部件。这些允许用户输入或编辑关于第一列中的信息的数据。

下图显示了表单布局的实际工作方式:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

左栏由标签组成,而右栏由输入部件组成。如果您正在开发一个数据库应用程序,那么这种布局可能是一个有用的工具,可以在创建输入表单时提高您的生产率。

下面的例子展示了如何创建一个使用QFormLayout对象来排列小部件的应用程序:

 1# f_layout.py
 2
 3"""Form layout example."""
 4
 5import sys
 6
 7from PyQt6.QtWidgets import (
 8    QApplication,
 9    QFormLayout,
10    QLineEdit,
11    QWidget,
12)
13
14app = QApplication([])
15window = QWidget()
16window.setWindowTitle("QFormLayout")
17
18layout = QFormLayout()
19layout.addRow("Name:", QLineEdit())
20layout.addRow("Age:", QLineEdit())
21layout.addRow("Job:", QLineEdit())
22layout.addRow("Hobbies:", QLineEdit())
23window.setLayout(layout)
24
25window.show()
26sys.exit(app.exec())

在这个例子中,第 18 行到第 23 行做了大量的工作。QFormLayout有一个方便的方法叫.addRow()。您可以使用此方法向布局中添加一个包含两个小部件的行。.addRow()的第一个参数应该是标签或字符串。然后,第二个参数可以是允许用户输入或编辑数据的任何小部件。在这个具体示例中,您使用了线编辑。

如果您运行这段代码,您将会看到一个如下所示的窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上图显示了一个使用表单布局的窗口。第一列包含向用户询问一些信息的标签。第二列显示允许用户输入或编辑所需信息的小部件。

Remove ads

对话框

使用 PyQt,您可以开发两种类型的 GUI 桌面应用程序。根据您用来创建主窗体或窗口的类,您将拥有以下内容之一:

  1. **一个主窗口风格的应用:**该应用的主窗口继承了 QMainWindow
  2. **一个对话框风格的应用:**应用的主窗口继承了 QDialog

您将首先从对话框样式的应用程序开始。在下一节中,您将了解主窗口风格的应用程序。

要开发一个对话框风格的应用,需要创建一个从QDialog继承的 GUI 类,它是所有对话框窗口的基类。一个对话窗口是一个独立的窗口,你可以用它作为你的应用程序的主窗口。

**注意:**对话窗口通常用在主窗口风格的应用程序中,用于与用户进行简短的交流和交互。

当您使用对话窗口与用户交流时,这些对话可以是:

  • **模态:**阻止输入到同一应用程序中任何其他可见窗口。你可以通过调用它的.exec()方法来显示一个模态对话框。
  • **无模式:**在同一个应用程序中独立于其他窗口运行。你可以通过使用它的.show()方法来显示一个非模态对话框。

对话框窗口也可以提供一个返回值,并有默认按钮,如OkCancel

对话框总是一个独立的窗口。如果一个对话框有一个parent,那么它将在父窗口小部件的顶部居中显示。有父级的对话框将共享父级的任务栏条目。如果你没有为一个给定的对话框设置parent,那么这个对话框将会在系统的任务栏中获得它自己的条目。

下面是一个如何使用QDialog开发对话框风格的应用程序的例子:

 1# dialog.py
 2
 3"""Dialog-style application."""
 4
 5import sys
 6
 7from PyQt6.QtWidgets import (
 8    QApplication,
 9    QDialog,
10    QDialogButtonBox,
11    QFormLayout,
12    QLineEdit,
13    QVBoxLayout,
14)
15
16class Window(QDialog):
17    def __init__(self):
18        super().__init__(parent=None)
19        self.setWindowTitle("QDialog")
20        dialogLayout = QVBoxLayout()
21        formLayout = QFormLayout()
22        formLayout.addRow("Name:", QLineEdit())
23        formLayout.addRow("Age:", QLineEdit())
24        formLayout.addRow("Job:", QLineEdit())
25        formLayout.addRow("Hobbies:", QLineEdit())
26        dialogLayout.addLayout(formLayout)
27        buttons = QDialogButtonBox()
28        buttons.setStandardButtons(
29            QDialogButtonBox.StandardButton.Cancel
30            | QDialogButtonBox.StandardButton.Ok
31        )
32        dialogLayout.addWidget(buttons)
33        self.setLayout(dialogLayout)
34
35if __name__ == "__main__":
36    app = QApplication([])
37    window = Window()
38    window.show()
39    sys.exit(app.exec())

这个应用程序稍微复杂一点。下面是这段代码的作用:

  • 第 16 行通过继承QDialog为应用程序的 GUI 定义了一个Window类。
  • 第 18 行使用 super() 调用父类的 .__init__() 方法。该调用允许您正确初始化该类的实例。在这个例子中,parent参数被设置为 None ,因为这个对话框将是你的主窗口。
  • 第 19 行设置窗口的标题。
  • 第 20 行dialogLayout分配一个QVBoxLayout对象。
  • 第 21 行formLayout分配一个QFormLayout对象。
  • 第 22 到 25 行formLayout添加小部件。
  • 26 线dialogLayout呼叫.addLayout()。该调用将表单布局嵌入到全局对话框布局中。
  • 第 27 行定义了一个按钮框,它提供了一个方便的空间来显示对话框的按钮。
  • 第 28 到 31 行向对话框添加两个标准按钮OkCancel
  • 第 32 行通过调用.addWidget()将按钮框添加到对话框中。

if __name__ == "__main__": 构造包装了应用程序的主要代码。这种条件语句在 Python 应用中很常见。它确保缩进的代码只有在包含文件作为程序执行而不是作为模块导入时才会运行。关于这个构造的更多信息,请查看 Python 中的ifname= = "main"做什么?

**注意:**在上面例子的第 26 行,你会注意到布局管理器可以相互嵌套。您可以通过调用容器布局上的.addLayout()来嵌套布局,并将嵌套布局作为参数。

上面的代码示例将显示一个类似如下的窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下图显示了您创建的 GUI,它使用一个QFormLayout对象来排列小部件,使用一个QVBoxLayout布局来显示应用程序的全局布局。

主窗口

大多数时候,你的 GUI 应用程序是主窗口风格的应用程序。这意味着他们将会有一个菜单栏,一些工具栏,一个状态栏,以及一个将会成为 GUI 主要元素的中央部件。你的应用程序通常有几个对话框来完成依赖于用户输入的辅助动作。

您将继承QMainWindow来开发主窗口风格的应用程序。从QMainWindow派生的类的实例被认为是应用程序的主窗口,应该是唯一的。

提供了一个快速构建应用程序 GUI 的框架。该类有自己的内置布局,接受以下图形组件:

成分 在窗口上的位置 描述
一个菜单栏 顶端 保存应用程序的主菜单
一个或多个工具栏 侧面 按住工具按钮和其他小工具,如QComboBoxQSpinBox
一个中央部件 中心 保存窗口的中心小部件,它可以是任何类型,包括复合小部件
一个或多个停靠小部件 围绕中心部件 是小的、可移动的、可隐藏的窗户
一个状态栏 底部 持有应用程序的状态栏,显示状态信息

没有中央 widget,您不能创建主窗口。你需要一个中心部件,即使它只是一个占位符。在这种情况下,您可以使用一个QWidget对象作为您的中心小部件。

你可以用.setCentralWidget()方法设置窗口的中心部件。主窗口的布局将只允许你有一个中央部件,但它可以是一个单一的或复合的部件。下面的代码示例向您展示了如何使用QMainWindow创建一个主窗口样式的应用程序:

 1# main_window.py
 2
 3"""Main window-style application."""
 4
 5import sys
 6
 7from PyQt6.QtWidgets import (
 8    QApplication,
 9    QLabel,
10    QMainWindow,
11    QStatusBar,
12    QToolBar,
13)
14
15class Window(QMainWindow):
16    def __init__(self):
17        super().__init__(parent=None)
18        self.setWindowTitle("QMainWindow")
19        self.setCentralWidget(QLabel("I'm the Central Widget"))
20        self._createMenu()
21        self._createToolBar()
22        self._createStatusBar()
23
24    def _createMenu(self):
25        menu = self.menuBar().addMenu("&Menu")
26        menu.addAction("&Exit", self.close)
27
28    def _createToolBar(self):
29        tools = QToolBar()
30        tools.addAction("Exit", self.close)
31        self.addToolBar(tools)
32
33    def _createStatusBar(self):
34        status = QStatusBar()
35        status.showMessage("I'm the Status Bar")
36        self.setStatusBar(status)
37
38if __name__ == "__main__":
39    app = QApplication([])
40    window = Window()
41    window.show()
42    sys.exit(app.exec())

下面是这段代码的工作原理:

  • 第 15 行创建了一个从QMainWindow继承而来的类Window
  • 第 16 行定义了类初始化器。
  • 第 17 行调用基类的初始化器。同样,parent参数被设置为None,因为这是你的应用程序的主窗口,所以它不能有父窗口。
  • 第 18 行设置窗口的标题。
  • 第 19 行设置一个QLabel作为窗口的中心部件。
  • 第 20 到 22 行调用非公共方法来创建不同的 GUI 元素:
    • 第 24 行到第 26 行创建主菜单栏,其中有一个名为菜单的下拉菜单。该菜单将有一个菜单选项来退出应用程序。
    • 第 28 行到第 31 行创建工具栏,工具栏上有一个退出应用程序的按钮。
    • 第 33 到 36 行创建应用程序的状态栏。

当您使用 GUI 组件自己的方法实现它们时,就像您在这个例子中对菜单栏、工具栏和状态栏所做的那样,您正在使您的代码更具可读性和可维护性。

**注意:**如果你在 macOS 上运行这个例子,那么你可能会对应用程序的主菜单有问题。macOS 隐藏了某些菜单选项,比如退出。请记住,macOS 在屏幕顶部的应用程序条目下显示了退出退出选项。

当您运行上面的示例应用程序时,您将得到如下所示的窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如您所确认的,您的主窗口样式的应用程序具有以下组件:

  • 一个总称为菜单的主菜单
  • 一个带有退出工具按钮的工具栏
  • 一个中央小部件由一个带有文本消息的QLabel对象组成
  • 窗口底部有一个状态栏

就是这样!您已经学习了如何使用 Python 和 PyQt 构建主窗口样式的应用程序。到目前为止,您已经了解了 PyQt 的小部件集中一些更重要的图形组件。在接下来的几节中,您将学习与使用 PyQt 构建 GUI 应用程序相关的其他重要概念。

Remove ads

应用程序

是开发 PyQt GUI 应用程序时最基础的类。这个类是任何 PyQt 应用程序的核心组件。它管理应用程序的控制流及其主要设置。

在 PyQt 中,QApplication的任何实例都是一个应用程序。每个 PyQt GUI 应用程序必须有一个QApplication实例。该班的一些职责包括:

  • 处理应用的初始化结束
  • 提供事件循环和事件处理
  • 处理大多数系统范围和应用范围的设置
  • 提供对全局信息的访问,例如应用程序的目录、屏幕大小等等
  • 解析常见的命令行参数
  • 定义应用程序的外观和感觉
  • 提供本地化功能

这些只是QApplication的部分核心职责。因此,这是开发 PyQt GUI 应用程序的一个基本类。

QApplication最重要的职责之一就是提供事件循环和整个事件处理机制。在下一节中,您将仔细了解什么是事件循环以及它是如何工作的。

事件循环

GUI 应用程序是由事件驱动的。这意味着调用函数和方法来响应用户操作,如单击按钮、从组合框中选择一项、在文本编辑中输入或更新文本、按键盘上的键等等。这些用户动作通常被称为事件

事件由事件循环处理,也称为主循环。事件循环是一个无限循环,其中来自用户、窗口系统和任何其他源的所有事件都被处理和调度。事件循环等待一个事件发生,然后分派它执行某个任务。事件循环继续工作,直到应用程序终止。

所有 GUI 应用程序都有一个事件循环。当一个事件发生时,循环检查它是否是一个**终止事件。**在这种情况下,循环结束,应用程序退出。否则,事件将被发送到应用程序的事件队列进行进一步处理,循环将再次迭代。在 PyQt6 中,可以通过调用QApplication对象上的.exec()来运行应用程序的事件循环。

对于触发动作的事件,您需要将事件与您想要执行的动作相连接。在 PyQt 中,您可以用信号和插槽机制建立这种连接,这将在下一节中探讨。

信号和插槽

PyQt 小部件充当事件捕捉器。这意味着每个小部件都可以捕捉特定的事件,比如鼠标点击、按键等等。作为对这些事件的响应,一个小部件发出一个信号,这是一种宣布其状态变化的消息。

信号本身不执行任何操作。如果你想要一个信号来触发一个动作,那么你需要把它连接到一个插槽。这是一个函数或方法,每当相关的信号发出时,它就会执行一个动作。你可以使用任何 Python 可调用的作为槽。

如果一个信号被连接到一个插槽,那么每当这个信号被发出时,这个插槽就会被调用。如果信号没有连接到任何插槽,那么什么都不会发生,信号会被忽略。信号和插槽的一些最相关的功能包括:

  • 一个信号可以连接到一个或多个插槽。
  • 一个信号也可以连接到另一个信号。
  • 一个插槽可以连接到一个或多个信号。

您可以使用以下语法来连接信号和插槽:

widget.signal.connect(slot_function)

这将把slot_function连接到widget.signal。从现在开始,每当发射.signal的时候,就会叫slot_function()

下面的代码展示了如何在 PyQt 应用程序中使用信号和插槽机制:

 1# signals_slots.py
 2
 3"""Signals and slots example."""
 4
 5import sys
 6
 7from PyQt6.QtWidgets import (
 8    QApplication,
 9    QLabel,
10    QPushButton,
11    QVBoxLayout,
12    QWidget,
13)
14
15def greet(): 16    if msgLabel.text():
17        msgLabel.setText("")
18    else:
19        msgLabel.setText("Hello, World!")
20
21app = QApplication([])
22window = QWidget()
23window.setWindowTitle("Signals and slots")
24layout = QVBoxLayout()
25
26button = QPushButton("Greet")
27button.clicked.connect(greet) 28
29layout.addWidget(button)
30msgLabel = QLabel("")
31layout.addWidget(msgLabel)
32window.setLayout(layout)
33window.show()
34sys.exit(app.exec())

在第 15 行,您创建了greet(),您将把它用作一个槽。然后在第 27 行,你连接按钮的信号到greeting()。这样,每当用户点击问候按钮,就会调用greet()槽,标签对象的文本在Hello, World!和空字符串之间交替:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当你点击问候按钮时,Hello, World!消息在你的应用程序主窗口上出现和消失。

**注意:**每个部件都有自己的一组预定义信号。您可以在小部件的文档中查看它们。

如果你的 slot 函数需要接收额外的参数,那么你可以使用 functools.partial() 来传递它们。例如,您可以修改greet()来获取一个参数,如下面的代码所示:

# signals_slots.py
# ...

def greet(name):
    if msg.text():
        msg.setText("")
    else:
 msg.setText(f"Hello, {name}") 
# ...

现在greet()需要接收一个名为name的参数。如果你想把这个新版本的greet()连接到.clicked信号上,你可以这样做:

# signals_slots.py

"""Signals and slots example."""

import sys
from functools import partial 
# ...

button = QPushButton("Greet")
button.clicked.connect(partial(greeting, "World!")) 
# ...

要让这段代码工作,首先需要从 functools 导入partial()。对partial()的调用返回一个函数对象,当用name="World!"调用时,其行为类似于greet()。现在,当用户点击按钮时,消息Hello, World!会像以前一样出现在标签中。

**注意:**你也可以使用 lambda 函数将一个信号连接到一个需要额外参数的插槽。作为一个练习,试着用lambda代替functools.partial()来编写上面的例子。

您将使用信号和插槽机制赋予 PyQt GUI 应用程序生命。这种机制将允许您将用户事件转化为具体的动作。您可以通过查看关于该主题的 PyQt6 文档来更深入地了解信号和插槽。

现在您已经了解了 PyQt 的几个重要概念的基础。有了这些知识和库的文档,您就可以开始开发自己的 GUI 应用程序了。在下一节中,您将构建您的第一个全功能 GUI 应用程序。

Remove ads

用 Python 和 PyQt 创建计算器应用程序

在本节中,您将使用模型-视图-控制器(MVC) 设计模式开发一个计算器 GUI 应用程序。这个模式有三层代码,每一层都有不同的角色:

  1. 模型负责你的应用程序的业务逻辑。它包含核心功能和数据。在您的计算器应用程序中,模型将处理输入值和计算。

  2. 视图实现了你的应用程序的 GUI。它托管最终用户与应用程序交互所需的所有小部件。该视图还接收用户的动作和事件。对于您的示例,视图将是屏幕上的计算器窗口。

  3. 控制器连接模型和视图,使应用程序工作。用户的事件或请求被发送到控制器,控制器使模型开始工作。当模型以正确的格式交付请求的结果或数据时,控制器将其转发给视图。在您的计算器应用程序中,控制器将从 GUI 接收目标数学表达式,要求模型执行计算,并用结果更新 GUI。

以下是对您的 GUI 计算器应用程序如何工作的逐步描述:

  1. 用户在视图(GUI)上执行动作或请求(事件)
  2. 视图通知控制器用户的动作。
  3. 控制器得到用户的请求,向模型查询响应。
  4. 该模型处理控制器的查询,执行所需的计算,并返回结果
  5. 控制器接收模型的响应,相应地更新视图
  6. 用户最终在视图上看到请求的结果。

您将使用这种 MVC 设计通过 Python 和 PyQt 构建您的计算器应用程序。

为你的 PyQt 计算器应用程序创建框架

首先,您将在一个名为pycalc.py的文件中为您的应用程序实现一个最小的框架。您可以通过单击下面的链接获得该文件以及计算器应用程序的其余源代码:

下载代码: 点击此处下载代码,您将在本教程中使用用 PyQt 在 Python 中构建一个计算器。

如果您更喜欢自己编写项目代码,那么就在您当前的工作目录中创建pycalc.py。在您最喜欢的代码编辑器或 IDE 中打开文件,并键入以下代码:

 1# pycalc.py
 2
 3"""PyCalc is a simple calculator built with Python and PyQt."""
 4
 5import sys
 6
 7from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget
 8
 9WINDOW_SIZE = 235
10
11class PyCalcWindow(QMainWindow):
12    """PyCalc's main window (GUI or view)."""
13
14    def __init__(self):
15        super().__init__()
16        self.setWindowTitle("PyCalc")
17        self.setFixedSize(WINDOW_SIZE, WINDOW_SIZE)
18        centralWidget = QWidget(self)
19        self.setCentralWidget(centralWidget)
20
21def main():
22    """PyCalc's main function."""
23    pycalcApp = QApplication([])
24    pycalcWindow = PyCalcWindow()
25    pycalcWindow.show()
26    sys.exit(pycalcApp.exec())
27
28if __name__ == "__main__":
29    main()

这个脚本实现了运行基本 GUI 应用程序所需的所有样板代码。您将使用这个框架来构建您的计算器应用程序。

下面是这段代码的工作原理:

  • 5 号线进口sys。这个模块提供了exit()函数,您将使用它来干净地终止应用程序。

  • 第 7 行PyQt6.QtWidgets导入所需的类。

  • 第 9 行创建一个 Python 常量来为你的计算器应用程序保存一个固定的像素大小的窗口。

  • 第 11 行创建了PyCalcWindow类来提供应用程序的 GUI。注意这个类继承自QMainWindow

  • 第 14 行定义了类初始化器。

  • 第 15 行调用超类上的.__init__()进行初始化。

  • 第 16 行将窗口的标题设置为"PyCalc"

  • 第 17 行使用.setFixedSize()给窗口一个固定的大小。这确保了用户在应用程序执行期间无法调整窗口大小。

  • 第 18 行和第 19 行创建一个QWidget对象,并将其设置为窗口的中心小部件。该对象将是您的计算器应用程序中所有必需的 GUI 组件的父对象。

  • 第 21 行定义了你的计算器的主功能。拥有这样的main()函数是 Python 中的最佳实践。这个函数提供了应用程序的入口点。在main()中,您的程序执行以下操作:

    • 第 23 行创建一个名为pycalcAppQApplication对象。
    • 第 24 行创建应用程序窗口的实例pycalcWindow
    • 第 25 行通过调用窗口对象上的.show()来显示 GUI。
    • 第 26 行.exec()运行应用程序的事件循环。

最后,第 29 行调用main()来执行您的计算器应用程序。运行上述脚本时,屏幕上会出现以下窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

就是这样!您已经成功地为 GUI 计算器应用程序构建了一个功能齐全的应用程序框架。现在,您已经准备好继续构建项目了。

完成应用程序视图

你现在的 GUI 看起来并不像一个计算器。您需要通过添加一个显示目标数学运算的显示器和一个表示数字和基本数学运算符的按钮键盘来完成这个 GUI。您还将添加代表其他所需符号和动作的按钮,如清除显示。

首先,您需要更新您的导入,如以下代码所示:

# pycalc.py

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
    QApplication,
 QGridLayout, QLineEdit,    QMainWindow,
 QPushButton, QVBoxLayout,    QWidget,
)

# ...

您将使用一个QVBoxLayout布局管理器来管理计算器的全局布局。要排列按钮,您将使用一个QGridLayout对象。QLineEdit类将作为计算器的显示,QPushButton将提供所需的按钮。

现在你可以更新PyCalcWindow的初始化器了:

# pycalc.py
# ...

class PyCalcWindow(QMainWindow):
    """PyCalc's main window (GUI or view)."""

    def __init__(self):
        super().__init__()
        self.setWindowTitle("PyCalc")
        self.setFixedSize(WINDOW_SIZE, WINDOW_SIZE)
 self.generalLayout = QVBoxLayout()        centralWidget = QWidget(self)
 centralWidget.setLayout(self.generalLayout)        self.setCentralWidget(centralWidget)
 self._createDisplay() self._createButtons() 
# ...

您已经添加了突出显示的代码行。您将使用.generalLayout作为应用程序的总体布局。在这种布局中,您将在顶部放置显示屏,在底部放置网格布局中的键盘按钮。

此时对._createDisplay()._createButtons()的调用将不起作用,因为您还没有实现那些方法。要解决这个问题,你将从编码._createDisplay()开始。

回到代码编辑器,更新pycalc.py,如下所示:

# pycalc.py
# ...

WINDOW_SIZE = 235
DISPLAY_HEIGHT = 35 
class PyCalcWindow(QMainWindow):
    # ...

 def _createDisplay(self):        self.display = QLineEdit()
        self.display.setFixedHeight(DISPLAY_HEIGHT)
        self.display.setAlignment(Qt.AlignmentFlag.AlignRight)
        self.display.setReadOnly(True)
        self.generalLayout.addWidget(self.display)

# ...

在这个代码片段中,首先定义一个新的常数来保存以像素为单位的显示高度。然后你在PyCalcWindow里面定义._createDisplay()

要创建计算器的显示,您需要使用一个QLineEdit小部件。然后,使用DISPLAY_HEIGHT常量为显示器设置一个 35 像素的固定高度。显示器将使其文本左对齐。最后,显示将是只读的,以防止用户直接编辑。最后一行代码将显示添加到计算器的一般布局中。

接下来,您将实现._createButtons()方法来为计算器的键盘创建所需的按钮。这些按钮将存在于网格布局中,因此您需要一种方法来表示它们在网格上的坐标。每个坐标对将由一行和一列组成。为了表示一个坐标对,你将使用一个列表的列表。每个嵌套列表将代表一行。

现在继续用下面的代码更新pycalc.py文件:

# pycalc.py
# ...

WINDOW_SIZE = 235
DISPLAY_HEIGHT = 35
BUTTON_SIZE = 40 
# ...

在这段代码中,您定义了一个名为BUTTON_SIZE的新常量。您将使用该常量来提供计算器按钮的大小。在这个具体的例子中,所有的按钮都是正方形,每边有 40 个像素。

有了这个初始设置,您就可以编写._createButtons()方法了。您将使用一个列表来保存按键或按钮以及它们在计算器键盘上的位置。一个QGridLayout将允许您排列计算器窗口上的按钮:

# pycalc.py
# ...

class PyCalcWindow(QMainWindow):
    # ...

 def _createButtons(self):        self.buttonMap = {}
        buttonsLayout = QGridLayout()
        keyBoard = [
            ["7", "8", "9", "/", "C"],
            ["4", "5", "6", "*", "("],
            ["1", "2", "3", "-", ")"],
            ["0", "00", ".", "+", "="],
        ]

        for row, keys in enumerate(keyBoard):
            for col, key in enumerate(keys):
                self.buttonMap[key] = QPushButton(key)
                self.buttonMap[key].setFixedSize(BUTTON_SIZE, BUTTON_SIZE)
                buttonsLayout.addWidget(self.buttonMap[key], row, col)

        self.generalLayout.addLayout(buttonsLayout)

# ...

首先创建一个空字典self.buttonMap来保存计算器按钮。然后,创建一个列表列表来存储键标签。每一行或嵌套列表将代表网格布局中的一行,而每个键标签的索引将代表布局中的相应列。

然后你定义两个 for循环。外部循环遍历行,内部循环遍历列。在内部循环中,你创建按钮并把它们添加到self.buttonMapbuttonsLayout中。每个按钮都有固定的40x40像素大小,你可以用.setFixedSize()BUTTON_SIZE常量来设置。

最后,通过调用.generalLayout对象上的.addLayout(),将网格布局嵌入到计算器的总体布局中。

注意:谈到小部件尺寸,您很少会在 PyQt 文档中找到度量单位。度量单位假定为像素,除非您使用 QPrinter 类,该类使用

现在,您的计算器的 GUI 将优雅地显示显示屏和按钮。但是,您无法更新显示器上显示的信息。您可以通过向PyCalcWindow添加一些额外的方法来解决这个问题:

方法 描述
.setDisplayText() 设置和更新显示的文本
.displayText() 获取当前显示的文本
.clearDisplay() 清除显示的文本

这些方法将提供 GUI 的公共接口,并完成 Python 计算器应用程序的视图类。

下面是一个可能的实现:

# pycalc.py
# ...

class PyCalcWindow(QMainWindow):
    # ...

 def setDisplayText(self, text):        """Set the display's text."""
        self.display.setText(text)
        self.display.setFocus()

 def displayText(self):        """Get the display's text."""
        return self.display.text()

 def clearDisplay(self):        """Clear the display."""
        self.setDisplayText("")

# ...

下面是每种方法的功能分类:

  • .setDisplayText() 使用.setText()来设置和更新显示的文本。它还使用.setFocus()来设置光标在显示屏上的焦点。

  • .displayText() 是一个 getter 方法,返回显示的当前文本。当用户点击计算器键盘上的等号(=)时,应用程序将使用.displayText()的返回值作为要计算的数学表达式。

  • .clearDisplay() 将显示的文本设置为空字符串(""),以便用户可以引入新的数学表达式。每当用户按下计算器面板上的 C 按钮时,该方法就会被触发。

现在,您的计算器的图形用户界面已经可以使用了!当您运行该应用程序时,您将得到如下窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你已经完成了计算器的图形用户界面,它看起来非常光滑!然而,如果你试图做一些计算,那么计算器不会像预期的那样响应。这是因为您还没有实现模型和控制器组件。在下一节中,您将编写计算器的模型。

Remove ads

实现计算器的模型

在 MVC 模式中,模型是负责业务逻辑的代码层。在您的计算器应用程序中,业务逻辑都是关于基本的数学计算。因此,您的模型将评估用户在计算器的 GUI 中引入的数学表达式。

计算器的模型也需要处理错误。为此,您将定义以下全局常数:

# pycalc.py
# ...

ERROR_MSG = "ERROR" WINDOW_SIZE = 235
# ...

这个ERROR_MSG常量是用户在计算器显示屏上看到的消息,如果他们引入了无效的数学表达式。

有了上面的更改,您就可以编写应用程序的模型了,在本例中,它将是一个单独的函数:

# pycalc.py
# ...

class PyCalcWindow(QMainWindow):
    # ...

def evaluateExpression(expression):
    """Evaluate an expression (Model)."""
    try:
        result = str(eval(expression, {}, {}))
    except Exception:
        result = ERROR_MSG
    return result

# ...

evaluateExpression()中,您使用 eval() 来计算以字符串形式出现的数学表达式。如果评估成功,那么你就返回result。否则,您将返回预定义的错误消息。注意,这个函数并不完美。它有几个重要的问题:

  • tryexcept 块不捕捉特定的异常,所以它使用了 Python 中不鼓励的一种做法。
  • 函数使用 eval() ,会导致一些严重的安全问题

你可以随意修改这个功能,使它更加可靠和安全。在本教程中,您将按原样使用该函数,以便将重点放在实现 GUI 上。

为您的计算器创建控制器类

在本节中,您将编写计算器的控制器类。这个类将视图连接到您刚刚编码的模型。您将使用控制器类让计算器执行动作来响应用户事件。

您的控制器类需要执行三个主要任务:

  1. 访问 GUI 的公共界面。
  2. 处理数学表达式的创建。
  3. 将所有按钮的.clicked信号连接到相应的插槽。

要执行所有这些操作,您需要马上编写一个新的PyCalc类。继续用下面的代码更新pycalc.py:

# pytcalc.py

import sys
from functools import partial
# ...

def evaluateExpression(expression):
    # ...

class PyCalc:
    """PyCalc's controller class."""

 def __init__(self, model, view):        self._evaluate = model
        self._view = view
        self._connectSignalsAndSlots()

 def _calculateResult(self):        result = self._evaluate(expression=self._view.displayText())
        self._view.setDisplayText(result)

 def _buildExpression(self, subExpression):        if self._view.displayText() == ERROR_MSG:
            self._view.clearDisplay()
        expression = self._view.displayText() + subExpression
        self._view.setDisplayText(expression)

 def _connectSignalsAndSlots(self):        for keySymbol, button in self._view.buttonMap.items():
            if keySymbol not in {"=", "C"}:
                button.clicked.connect(
                    partial(self._buildExpression, keySymbol)
                )
        self._view.buttonMap["="].clicked.connect(self._calculateResult)
        self._view.display.returnPressed.connect(self._calculateResult)
        self._view.buttonMap["C"].clicked.connect(self._view.clearDisplay)

# ...

pycalc.py的顶部,你从functools导入partial()。您将使用这个函数将信号与需要额外参数的方法连接起来。

PyCalc中,你定义了类初始化器,它有两个参数:应用的模型和它的视图。然后将这些参数存储在适当的实例属性中。最后,您调用._connectSignalsAndSlots()来进行所有需要的信号和插槽的连接。

._calculateResult()中,您使用._evaluate()来计算用户刚刚输入计算器显示屏的数学表达式。然后在计算器的视图上调用.setDisplayText(),用计算结果更新显示文本。

顾名思义,._buildExpression()方法负责构建目标数学表达式。为此,该方法将初始显示值与用户在计算器键盘上输入的每个新值连接起来。

最后,._connectSignalsAndSlots()方法将所有按钮的.clicked信号与控制器类中适当的插槽方法连接起来。

就是这样!你的控制器类已经准备好了。然而,为了让所有这些代码像真正的计算器一样工作,您需要更新应用程序的main()函数,如下面的代码所示:

# pytcalc.py
# ...

def main():
    """PyCalc's main function."""
    pycalcApp = QApplication([])
    pycalcWindow = PyCalcWindow()
    pycalcWindow.show()
 PyCalc(model=evaluateExpression, view=pycalcWindow)    sys.exit(pycalcApp.exec())

这段代码创建了一个新的PyCalc实例。PyCalc类构造函数的model参数保存了对evaluateExpression()函数的引用,而view参数保存了对pycalcWindow对象的引用,该对象提供了应用程序的 GUI。现在,您的 PyQt 计算器应用程序已经可以运行了。

Remove ads

运行计算器

现在你已经用 Python 和 PyQt 写好了计算器应用,是时候进行现场测试了!如果您从命令行运行该应用程序,那么您将得到类似如下的结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

要使用 PyCalc,请用鼠标输入有效的数学表达式。然后,按 Enter 或点击等号(=)按钮进行计算,并将表达式结果显示在计算器的显示屏上。就是这样!您已经用 Python 和 PyQt 开发了第一个全功能的 GUI 桌面应用程序!

附加工具

PyQt6 提供了一组有用的附加工具,可以帮助您构建可靠的、现代的、功能全面的 GUI 应用程序。与 PyQt 相关的一些最引人注目的工具包括 Qt 设计器国际化工具包

Qt Designer 允许您使用拖放界面来设计和构建图形用户界面。通过使用屏幕上的表单和拖放机制,您可以使用该工具来设计小部件、对话框和主窗口。以下动画展示了 Qt Designer 的一些功能:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Qt Designer 使用 XML .ui文件来存储你的 GUI 设计。PyQt 包括一个名为 uic 的模块来帮助处理.ui文件。您还可以使用名为 pyuic6 的命令行工具将.ui文件内容转换成 Python 代码。

**注意:**要深入 Qt Designer 并更好地理解如何使用该工具创建图形用户界面,请查看 Qt Designer 和 Python:更快地构建您的 GUI 应用程序

PyQt6 还提供了一套全面的工具,用于将应用程序国际化为本地语言。 pylupdate6 命令行工具创建并更新翻译(.ts)文件,其中可以包含接口字符串的翻译。如果你更喜欢 GUI 工具,那么你可以使用 Qt 语言学家来创建和更新带有接口字符串翻译的.ts文件。

结论

图形用户界面(GUI)应用程序仍然占据着软件开发市场的大部分份额。Python 提供了一些框架和库,可以帮助您开发现代的、健壮的 GUI 应用程序。

在本教程中,您学习了如何使用 PyQt ,这是 Python 中 GUI 应用程序开发最流行和最可靠的库之一。现在您知道了如何有效地使用 PyQt 来构建现代 GUI 应用程序。

在本教程中,您已经学会了如何:

  • 用 Python 和 PyQt 构建图形用户界面
  • 用户的事件app 的逻辑联系起来
  • 使用适当的项目布局组织一个 PyQt 应用程序
  • 用 PyQt 创建一个真实的 GUI 应用程序

现在,您可以使用 Python 和 PyQt 知识来赋予您自己的桌面 GUI 应用程序生命。是不是很酷?

您可以通过单击下面的链接获取计算器应用程序项目的源代码及其所有相关资源:

下载代码: 点击此处下载代码,您将在本教程中使用用 PyQt 在 Python 中构建一个计算器。

延伸阅读

要更深入地了解 PyQt 及其生态系统,请查看以下一些资源:

虽然 PyQt6 文档是这里列出的第一个资源,但是它的一些重要部分仍然缺失或不完整。幸运的是,您可以使用 Qt 文档来填补空白。************

PyQt 布局:创建专业外观的 GUI 应用程序

原文:https://realpython.com/python-pyqt-layout/

*立即观看**本教程有真实 Python 团队创建的相关视频课程。和书面教程一起看,加深你的理解: 为 GUI 应用程序创建 PyQt 布局

PyQt 的布局管理器提供了一种在 GUI 上排列图形组件或者小部件的用户友好且高效的方式。恰当地布局窗口小部件会让你的 GUI 应用程序看起来更加精致和专业。学会高效地这样做是使用 Python 和 PyQt 开始并运行 GUI 应用程序开发的一项基本技能。

在本教程中,您将学习:

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

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

为了更好地理解如何使用布局管理器,一些关于如何创建 PyQt GUI 应用程序以及如何使用 PyQt 窗口小部件的知识会有所帮助。

免费奖励: 掌握 Python 的 5 个想法,这是一个面向 Python 开发者的免费课程,向您展示将 Python 技能提升到下一个水平所需的路线图和心态。

在 GUI 上布置图形元素

当你创建图形用户界面(GUI) 应用程序时,一个常见的问题是如何让你的图形组件——T2 按钮、菜单、工具栏标签等等——在你的表单和窗口上连贯地布局。这个过程被称为 GUI 布局,它是创建 GUI 应用程序的重要步骤。

在过去,如果你想在一个窗口上布局图形组件,或者说小部件,那么你会遵循以下方法之一:

  1. 决定并手动设置窗口上每个小部件的静态大小和位置。
  2. 动态计算和设置每个小工具的大小和位置。

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

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

第二种方法更加灵活。然而,它也有缺点:

  • 你必须做大量的手工计算来确定每个部件的正确尺寸和位置。
  • 你必须做一些额外的计算来正确响应窗口大小调整
  • 每当你修改窗口的布局时,你必须重做所有的计算。

尽管你仍然可以使用这两种方法中的任何一种来布局你的 GUI,但是大多数时候你会希望使用第三种更方便的方法,这种方法是由大多数现代的 GUI 框架或工具包实现的:布局管理器。

注:在一些 GUI 框架中,如 Tkinter ,布局管理器也被称为几何管理器

布局管理器根据您的具体需求自动在 GUI 上排列小部件。它们避免了第一种方法的兼容性缺点以及第二种方法的烦人和复杂的计算。

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

Remove ads

获得 PyQt 布局图库

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

**注:**可惜 PyQt5 的官方文档有一些不完整的章节。要解决这个问题,您可以查看 PyQt4 文档,Python 文档的 Qt,或者原始的 Qt 文档

在本教程中,您会发现大多数链接会将您带到最初的 Qt 文档,在大多数情况下,这是一个更好的信息来源。

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

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

这些技术通常对应于您在上一节中看到的布局 GUI 的三种不同方法。

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

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

布局管理器是小部件和其他布局的容器。要将小部件添加到布局管理器,您可以在手边的布局上调用 .addWidget() 。要将一个布局添加到另一个布局中,您可以在手头的布局上调用.addLayout()。在嵌套布局构建复杂图形用户界面一节中,您将更深入地了解嵌套布局。

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

注意: QMainWindow 是一个 PyQt 类,您可以用它来创建主寡妇风格的应用程序。这个类有自己的内置布局管理器。所以,如果你使用的是QMainWindow,那么你通常不需要在主窗口对象上设置布局管理器。

布局中的所有微件都会自动设置为安装布局的微件的子件,而不是布局本身的子件。这是因为小部件只能有其他小部件,而不能有布局作为它们的父部件。

PyQt 的布局管理器提供了一些很酷的特性,让您在创建好看的 GUI 应用程序时更加轻松:

  • 无需任何计算即可处理微件的尺寸位置
  • 当用户调整底层窗口大小时,处理小部件的调整大小重新定位
  • 调整标签大小以更好地支持国际化
  • 多平台应用提供本地窗口布局

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

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

  1. QHBoxLayout 将小工具排列在一个水平框中。
  2. QVBoxLayout 将小工具排列在垂直框中。
  3. QGridLayout 将小工具排列成网格。
  4. QFormLayout 将小工具排成两列。

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

使用通用布局管理器

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

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

Remove ads

建筑平面布置:QHBoxLayout

盒子布局管理器从它们的父布局或窗口小部件中获取空间,将其分成许多盒子,或单元,并使布局中的每个窗口小部件填满一个盒子。

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

*要向一个QHBoxLayout对象添加小部件,您可以在布局对象上调用.addWidget(widget, stretch, alignment)。此方法采用一个必需参数和两个可选参数:

  1. widget 是一个必需的参数,用于保存要添加到布局中的特定小部件。

  2. stretch 是一个可选参数,它保存一个表示要应用于widget拉伸因子的整数。具有较高拉伸系数的窗口小部件在调整窗口大小时增长更多。默认为0,这意味着小部件没有分配拉伸因子。

  3. alignment 是可选参数,保存水平和垂直标志。您可以组合这些标志来产生小部件在其包含的单元格内的期望的对齐方式。它默认为0,这意味着小部件将填充整个单元格。

这里有一个小应用程序,展示了如何使用QHBoxLayout创建一个水平布局。在本例中,您将使用 QPushButton 对象来更好地显示每个小部件在布局中的位置,这取决于您将小部件添加到代码中的顺序:

 1import sys
 2
 3from PyQt5.QtWidgets import (
 4    QApplication,
 5    QHBoxLayout,
 6    QPushButton,
 7    QWidget,
 8)
 9
10class Window(QWidget):
11    def __init__(self):
12        super().__init__()
13        self.setWindowTitle("QHBoxLayout Example")
14        # Create a QHBoxLayout instance
15        layout = QHBoxLayout() 16        # Add widgets to the layout
17        layout.addWidget(QPushButton("Left-Most")) 18        layout.addWidget(QPushButton("Center"), 1) 19        layout.addWidget(QPushButton("Right-Most"), 2) 20        # Set the layout on the application's window
21        self.setLayout(layout) 22        print(self.children())
23
24if __name__ == "__main__":
25    app = QApplication(sys.argv)
26    window = Window()
27    window.show()
28    sys.exit(app.exec_())

在第 15 行,您创建了一个名为layoutQHBoxLayout对象。在第 17 到 19 行,您使用.addWidget()layout添加了三个按钮。请注意,您分别将12传递给中间最右边按钮中的stretch参数。在第 21 行,使用.setLayout()layout设置为窗口的顶层布局。

**注意:**如果你是使用 PyQt 进行 GUI 编程的新手,那么你可以看看 Python 和 PyQt:构建 GUI 桌面计算器来更好地了解如何使用 PyQt 创建 GUI 应用程序。

如果您运行这个应用程序,那么您会在屏幕上看到以下窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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

最右边的中心按钮有不同的拉伸系数,因此当您调整窗口大小时,它们会按比例扩展。

此外,layout中的所有按钮和布局本身都被设置为Window的子元素。这是由布局对象自动完成的,它在内部调用每个小部件上的.setParent()。第 22 行对 print() 的调用在您的终端上打印了Window的孩子的列表,作为这种行为的证据。

建筑竖向布局:QVBoxLayout

QVBoxLayout 垂直排列小工具**,一个在另一个下面。您可以使用这个类创建垂直布局,并从上到下排列您的小部件。由于QVBoxLayout是另一个盒子布局,其.addWidget()方法与QHBoxLayout中的方法相同。*

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

 1import sys
 2
 3from PyQt5.QtWidgets import (
 4    QApplication,
 5    QPushButton,
 6    QVBoxLayout,
 7    QWidget,
 8)
 9
10class Window(QWidget):
11    def __init__(self):
12        super().__init__()
13        self.setWindowTitle("QVBoxLayout Example")
14        self.resize(270, 110)
15        # Create a QVBoxLayout instance
16        layout = QVBoxLayout() 17        # Add widgets to the layout
18        layout.addWidget(QPushButton("Top")) 19        layout.addWidget(QPushButton("Center")) 20        layout.addWidget(QPushButton("Bottom")) 21        # Set the layout on the application's window
22        self.setLayout(layout)
23
24if __name__ == "__main__":
25    app = QApplication(sys.argv)
26    window = Window()
27    window.show()
28    sys.exit(app.exec_())

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

如果您运行这个应用程序,那么您将得到以下窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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

Remove ads

在网格中排列部件:QGridLayout

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

QGridLayout获取其父控件上的可用空间,将其分成行和列,并将每个小部件放入其自己的单元格或框中。QGridLayout根据小部件的数量及其坐标,自动计算出最终布局的行数和列数。如果你不给一个给定的单元格添加一个小部件,那么QGridLayout会让这个单元格为空。

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

  1. addWidget(widget, row, column, alignment) 在(rowcolumn)处的单元格增加widget
  2. addWidget(widget, fromRow, fromColumn, rowSpan, columnSpan, alignment)widget添加到单元格中,跨越多行、多列或两者。

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

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

下面是一个如何使用QGridLayout创建小部件网格的例子:

 1import sys
 2
 3from PyQt5.QtWidgets import (
 4    QApplication,
 5    QGridLayout,
 6    QPushButton,
 7    QWidget,
 8)
 9
10class Window(QWidget):
11    def __init__(self):
12        super().__init__()
13        self.setWindowTitle("QGridLayout Example")
14        # Create a QGridLayout instance
15        layout = QGridLayout() 16        # Add widgets to the layout
17        layout.addWidget(QPushButton("Button at (0, 0)"), 0, 0) 18        layout.addWidget(QPushButton("Button at (0, 1)"), 0, 1) 19        layout.addWidget(QPushButton("Button at (0, 2)"), 0, 2) 20        layout.addWidget(QPushButton("Button at (1, 0)"), 1, 0) 21        layout.addWidget(QPushButton("Button at (1, 1)"), 1, 1) 22        layout.addWidget(QPushButton("Button at (1, 2)"), 1, 2) 23        layout.addWidget(QPushButton("Button at (2, 0)"), 2, 0) 24        layout.addWidget(QPushButton("Button at (2, 1)"), 2, 1) 25        layout.addWidget(QPushButton("Button at (2, 2)"), 2, 2) 26        # Set the layout on the application's window
27        self.setLayout(layout)
28
29if __name__ == "__main__":
30    app = QApplication(sys.argv)
31    window = Window()
32    window.show()
33    sys.exit(app.exec_())

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

如果您从命令行运行这段代码,您将会看到如下窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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

.addWidget()的第二个实现中,参数widgetalignment保持不变,并且有四个额外的参数允许您将小部件放置在几行或几列中:

  1. fromRow 取一个整数,表示小部件将从哪一行开始。
  2. fromColumn 取一个整数,表示小部件将开始的列。
  3. rowSpan 取一个整数,表示小部件将在网格中占据的行数。
  4. columnSpan 取一个整数,表示小部件将在网格中占据的列数。

这里有一个应用程序展示了.addWidget()的这种变体是如何工作的:

 1import sys
 2
 3from PyQt5.QtWidgets import (
 4    QApplication,
 5    QGridLayout,
 6    QPushButton,
 7    QWidget,
 8)
 9
10class Window(QWidget):
11    def __init__(self):
12        super().__init__()
13        self.setWindowTitle("QGridLayout Example")
14        # Create a QGridLayout instance
15        layout = QGridLayout()
16        # Add widgets to the layout
17        layout.addWidget(QPushButton("Button at (0, 0)"), 0, 0)
18        layout.addWidget(QPushButton("Button at (0, 1)"), 0, 1)
19        layout.addWidget(QPushButton("Button Spans two Cols"), 1, 0, 1, 2) 20        # Set the layout on the application's window
21        self.setLayout(layout)
22
23if __name__ == "__main__":
24    app = QApplication(sys.argv)
25    window = Window()
26    window.show()
27    sys.exit(app.exec_())

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

**注意:**由于 PyQt 是为 Qt 绑定的 Python,是一组 C++ 库,所以有时候调用 PyQt 方法时不能使用关键字参数。上一段中使用的关键字参数的唯一目的是显示分配给每个参数的值。

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这种布局中,你可以让一个小部件占据多个单元格,就像你用按钮跨越两列按钮一样。

Remove ads

快速创建表单:QFormLayout

如果您经常创建表单来执行像将数据输入数据库这样的操作,那么 QFormLayout 就是为您准备的。这个类在一个两列布局中排列小部件。第一列通常显示一个描述预期输入的标签,第二列通常包含允许用户输入或编辑数据的输入小部件,如 QLineEditQComboBoxQSpinBox

要向表单布局添加小部件,可以使用.addRow()。这种方法有几种变体,但大多数情况下,您会从以下两种方法中进行选择:

  1. .addRow(label, field) 在表格布局底部添加新行。该行应该包含一个QLabel对象(label)和一个输入小部件(field)。

  2. .addRow(labelText, field) 自动创建并添加一个新的QLabel对象,其文本为labelTextfield持有一个输入控件。

下面是一个使用QFormLayout对象来排列小部件的示例应用程序:

 1import sys
 2
 3from PyQt5.QtWidgets import (
 4    QApplication,
 5    QFormLayout,
 6    QLabel,
 7    QLineEdit,
 8    QWidget,
 9)
10
11class Window(QWidget):
12    def __init__(self):
13        super().__init__()
14        self.setWindowTitle("QFormLayout Example")
15        self.resize(270, 110)
16        # Create a QFormLayout instance
17        layout = QFormLayout() 18        # Add widgets to the layout
19        layout.addRow("Name:", QLineEdit()) 20        layout.addRow("Job:", QLineEdit()) 21        emailLabel = QLabel("Email:")
22        layout.addRow(emailLabel, QLineEdit()) 23        # Set the layout on the application's window
24        self.setLayout(layout)
25
26if __name__ == "__main__":
27    app = QApplication(sys.argv)
28    window = Window()
29    window.show()
30    sys.exit(app.exec_())

在第 17 行,您创建了一个QFormLayout对象。然后,在第 19 到 22 行,向布局添加一些行。请注意,在第 19 行和第 20 行,您使用了该方法的第二种变体,在第 22 行,您使用了第一种变体,将一个QLabel对象作为第一个参数传递给.addRow()

如果您运行这段代码,您将在屏幕上看到以下窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用QFormLayout,你可以以两列的方式组织你的小部件。第一列包含要求用户提供一些信息的标签。第二列显示允许用户输入或编辑信息的小部件。

嵌套布局构建复杂的图形用户界面

您可以使用嵌套的布局来创建复杂的 GUI,这些 GUI 很难使用一个通用的 PyQt 布局管理器来创建。为此,您需要在一个外部布局上调用.addLayout()。这样,内部布局成为外部布局的子布局。

假设您需要创建一个对话框,在表单布局中显示一个标签和一行编辑,并且您希望在这些小部件下面放置几个垂直布局的复选框。这里有一个对话框的模型:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

蓝色矩形代表您的外部布局。绿色矩形是保存标签和行编辑的表单布局。红色矩形是放置选项复选框的垂直布局。绿色布局和红色布局都嵌套在蓝色布局中,蓝色布局是垂直布局。

下面是如何使用 PyQt 构建这种布局的示例:

 1import sys
 2
 3from PyQt5.QtWidgets import (
 4    QApplication,
 5    QCheckBox,
 6    QFormLayout,
 7    QLineEdit,
 8    QVBoxLayout,
 9    QWidget,
10)
11
12class Window(QWidget):
13    def __init__(self):
14        super().__init__()
15        self.setWindowTitle("Nested Layouts Example")
16        # Create an outer layout
17        outerLayout = QVBoxLayout() 18        # Create a form layout for the label and line edit
19        topLayout = QFormLayout() 20        # Add a label and a line edit to the form layout
21        topLayout.addRow("Some Text:", QLineEdit()) 22        # Create a layout for the checkboxes
23        optionsLayout = QVBoxLayout() 24        # Add some checkboxes to the layout
25        optionsLayout.addWidget(QCheckBox("Option one")) 26        optionsLayout.addWidget(QCheckBox("Option two")) 27        optionsLayout.addWidget(QCheckBox("Option three")) 28        # Nest the inner layouts into the outer layout
29        outerLayout.addLayout(topLayout) 30        outerLayout.addLayout(optionsLayout) 31        # Set the window's main layout
32        self.setLayout(outerLayout)
33
34if __name__ == "__main__":
35    app = QApplication(sys.argv)
36    window = Window()
37    window.show()
38    sys.exit(app.exec_())

下面是您在这段代码中要做的事情:

  • 在第 17 行中,你创建了外部或者顶层布局,你将把它作为父布局和窗口的主布局。在这种情况下,您使用QVBoxLayout,因为您希望小部件在表单上垂直排列。在你的模型中,这是蓝色的布局。
  • 在第 19 行,您创建一个表单布局来保存一个标签和一个行编辑。
  • 在第 21 行,您将所需的小部件添加到布局中。这相当于你的绿色布局。
  • 在第 23 行上,你创建一个垂直布局来放置复选框。
  • 在第 25 到 27 行上,添加所需的复选框。这是你的红色布局。
  • 在第 29 行和第 30 行,你在outerLayout下嵌套topLayoutoptionsLayout

就是这样!如果您运行该应用程序,您将看到如下窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这个应用程序中,您将两个不同的布局嵌套在一个外部布局下,为您的窗口创建一个通用布局。在窗口顶部,使用水平布局放置标签和行编辑。然后使用垂直布局在下面放置一些复选框。

Remove ads

使用多页面布局和小部件

到目前为止,您已经看到了如何使用传统的或通用的布局管理器来安排应用程序窗口中的小部件。这些布局管理器将在单页布局上排列小部件。换句话说,您的 GUI 将总是向用户显示同一组小部件。

有时,您需要创建一个布局来显示一组不同的小部件,以响应 GUI 上的某些用户操作。例如,如果你正在为一个给定的应用程序创建一个首选项对话框,那么你可能希望向用户呈现一个基于选项卡的,或者多页,其中每个选项卡或页面包含一组不同的密切相关的选项。每当用户点击一个标签或页面,应用程序就会显示不同的小部件。

PyQt 提供了一个名为 QStackedLayout 的内置布局,以及一些方便的小部件,如 QTabWidget ,允许您创建这种多页面布局。接下来的几节将带您了解这些工具。

创建一堆小部件

QStackedLayout 提供了一个布局管理器,允许你将你的小部件排列在上,一个在另一个之上。在这种布局中,一次只能看到一个小部件。

要用小部件填充堆叠布局,需要在布局对象上调用.addWidget()。这将把每个部件添加到布局内部部件列表的末尾。您还可以分别使用.insertWidget(index).removeWidget(widget)在小部件列表中的给定位置插入或移除小部件。

小部件列表中的每个小部件都显示为一个独立的页面。如果您想在一个页面上显示几个小部件,那么为每个页面使用一个QWidget对象,并为页面小部件设置一个适当的小部件布局。如果您需要获得布局中部件(页面)的总数,那么您可以调用.count()

使用QStackedLayout对象时要记住的重要一点是,您需要明确地提供一种在页面之间切换的机制。否则,您的布局将总是向用户显示相同的页面。要在页面之间切换,需要在 layout 对象上调用.setCurrentIndex()

下面的示例显示了如何使用带有组合框的堆叠布局在页面之间切换:

 1import sys
 2
 3from PyQt5.QtWidgets import (
 4    QApplication,
 5    QComboBox,
 6    QFormLayout,
 7    QLineEdit,
 8    QStackedLayout,
 9    QVBoxLayout,
10    QWidget,
11)
12
13class Window(QWidget):
14    def __init__(self):
15        super().__init__()
16        self.setWindowTitle("QStackedLayout Example")
17        # Create a top-level layout
18        layout = QVBoxLayout()
19        self.setLayout(layout)
20        # Create and connect the combo box to switch between pages
21        self.pageCombo = QComboBox() 22        self.pageCombo.addItems(["Page 1", "Page 2"]) 23        self.pageCombo.activated.connect(self.switchPage) 24        # Create the stacked layout
25        self.stackedLayout = QStackedLayout() 26        # Create the first page
27        self.page1 = QWidget() 28        self.page1Layout = QFormLayout() 29        self.page1Layout.addRow("Name:", QLineEdit()) 30        self.page1Layout.addRow("Address:", QLineEdit()) 31        self.page1.setLayout(self.page1Layout) 32        self.stackedLayout.addWidget(self.page1) 33        # Create the second page
34        self.page2 = QWidget() 35        self.page2Layout = QFormLayout() 36        self.page2Layout.addRow("Job:", QLineEdit()) 37        self.page2Layout.addRow("Department:", QLineEdit()) 38        self.page2.setLayout(self.page2Layout) 39        self.stackedLayout.addWidget(self.page2) 40        # Add the combo box and the stacked layout to the top-level layout
41        layout.addWidget(self.pageCombo)
42        layout.addLayout(self.stackedLayout)
43
44    def switchPage(self):
45        self.stackedLayout.setCurrentIndex(self.pageCombo.currentIndex())
46
47if __name__ == "__main__":
48    app = QApplication(sys.argv)
49    window = Window()
50    window.show()
51    sys.exit(app.exec_())

在第 21 到 23 行,您创建了一个QComboBox对象,它将允许您在布局中的页面之间切换。然后在一个列表中添加两个选项到组合框中,并将其连接到.switchPage(),这是为了处理页面切换。

.switchPage()中,您调用布局对象上的.setCurrentIndex(),将组合框的当前索引作为参数传递。这样,当用户更改组合框中的选项时,堆叠布局上的页面也会相应地更改。

在第 25 行,您创建了QStackedLayout对象。在第 27 到 32 行,您将第一页添加到布局中,在第 34 到 39 行,您将第二页添加到布局中。每个页面由一个QWidget对象表示,该对象包含几个布局方便的小部件。

让一切正常工作的最后一步是将组合框和布局添加到应用程序的主布局中。

下面是您的应用程序现在的行为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这种情况下,您的应用程序布局中有两个页面。每个页面由一个QWidget对象表示。当您在窗口顶部的组合框中选择一个新页面时,布局会发生变化以显示所选页面。

注: PyQt 提供了一个方便的类叫做 QStackedWidget ,构建在QStackedLayout之上。您也可以使用这个类来创建多页布局。

这个类提供了一个窗口小部件堆栈,其中一次只有一个窗口小部件可见。就像堆叠布局一样,QStackedWidget没有提供内在的页面切换机制。

除了堆叠布局和堆叠小部件,您还可以使用 QTabWidget 来创建多页面用户界面。您将在下一节中了解如何操作。

Remove ads

使用 PyQt 的选项卡小部件

在 PyQt 中创建多页排列的另一种流行方式是使用名为 QTabWidget 的类。这个类提供了一个标签栏和一个页面区域。您可以使用选项卡栏在页面和页面区域之间切换,以显示与所选选项卡相关联的页面。

默认情况下,选项卡栏位于页面区域的顶部。但是,您可以使用 .setTabPosition() 和四个可能的标签位置中的一个来改变这种行为:

制表位置 标签栏位置
QTabWidget.North 页面顶部
QTabWidget.South 页面底部
QTabWidget.West 页面左侧
QTabWidget.East 页面右侧

要向选项卡小部件添加选项卡,可以使用.addTab()。此方法有两种变体,即重载实现:

  1. .addTab(page, label)
  2. .addTab(page, icon, label)

在这两种情况下,该方法都会添加一个新的选项卡,用label作为选项卡的标题。page需要是一个小部件,代表与手头的选项卡相关联的页面。

在该方法的第二个变体中,icon需要是一个 QIcon 对象。如果你传递一个图标给.addTab(),那么这个图标将会显示在标签标题的左边。

创建选项卡小部件时的一个常见做法是为每个页面使用一个QWidget对象。这样,您将能够使用包含所需部件的布局向页面添加额外的部件。

大多数情况下,您将使用选项卡小部件为 GUI 应用程序创建对话框。这种布局允许你在相对较小的空间内为用户提供多种选择。你也可以利用标签系统根据一些分类标准来组织你的选项。

下面是一个示例应用程序,展示了如何创建和使用一个QTabWidget对象的基本知识:

 1import sys
 2
 3from PyQt5.QtWidgets import (
 4    QApplication,
 5    QCheckBox,
 6    QTabWidget,
 7    QVBoxLayout,
 8    QWidget,
 9)
10
11class Window(QWidget):
12    def __init__(self):
13        super().__init__()
14        self.setWindowTitle("QTabWidget Example")
15        self.resize(270, 110)
16        # Create a top-level layout
17        layout = QVBoxLayout()
18        self.setLayout(layout)
19        # Create the tab widget with two tabs
20        tabs = QTabWidget() 21        tabs.addTab(self.generalTabUI(), "General") 22        tabs.addTab(self.networkTabUI(), "Network") 23        layout.addWidget(tabs)
24
25    def generalTabUI(self): 26        """Create the General page UI."""
27        generalTab = QWidget()
28        layout = QVBoxLayout()
29        layout.addWidget(QCheckBox("General Option 1"))
30        layout.addWidget(QCheckBox("General Option 2"))
31        generalTab.setLayout(layout)
32        return generalTab
33
34    def networkTabUI(self): 35        """Create the Network page UI."""
36        networkTab = QWidget()
37        layout = QVBoxLayout()
38        layout.addWidget(QCheckBox("Network Option 1"))
39        layout.addWidget(QCheckBox("Network Option 2"))
40        networkTab.setLayout(layout)
41        return networkTab
42
43if __name__ == "__main__":
44    app = QApplication(sys.argv)
45    window = Window()
46    window.show()
47    sys.exit(app.exec_())

在这个例子中,您使用一个选项卡小部件向用户呈现一个简洁的对话框,显示与假想的首选项菜单的 GeneralNetwork 部分相关的选项。在第 20 行,您创建了QTabWidget对象。然后使用.addTab()向选项卡小部件添加两个选项卡。

.generalTabUI()networkTabUI()中,您为每个选项卡创建特定的 GUI。为此,您使用一个QWidget对象、一个QVBoxLayout对象和一些复选框来保存选项。

如果您现在运行该应用程序,您将在屏幕上看到以下对话框:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

就是这样!您拥有了一个功能齐全的基于选项卡的 GUI。请注意,要在页面之间切换,您只需单击相应的选项卡。

布置应用程序的主窗口

如果你使用 PyQt 来创建你的 GUI 应用程序,那么大多数时候你会使用 QMainWindow 来创建一个基于它的 GUI。这个类允许你创建主窗口风格的应用QMainWindow自带预定义布局。此布局允许您将以下图形组件添加到主窗口中:

对于大多数应用程序来说,除了中央小部件之外,所有这些图形组件都是可选的,中央小部件是应用程序正常工作所必需的。

**注意:**如果你使用QMainWindow创建 GUI 应用程序,那么你必须有一个中心部件,即使它只是一个占位符。

一些应用程序使用一个独特的全功能小部件作为它们的中心小部件。例如,如果你正在编写一个文本编辑器,那么你可能会使用一个 QTextEdit 对象作为编辑器的中心部件。

其他种类的 GUI 应用程序可能需要更复杂的中央小部件。在这种情况下,您可以使用一个QWidget对象作为您的中心小部件,然后创建一个布局,其中包含您的应用程序 GUI 所需的特定小部件排列。最后一步是将该布局设置为中心小部件的布局。

大多数时候,QMainWindow提供的布局足以创建任何种类的 GUI 应用程序。这种布局将有效地管理窗口小部件的行为,所以您不必担心这一点。

Remove ads

布局应用程序的对话框

GUI 应用程序通常使用一个主窗口和一个或多个对话框来构建。对话框是允许你和用户交流的小窗口。PyQt 提供了 QDialog 来处理对话框的创建。

QMainWindow不同,QDialog没有预定义或默认的顶层布局。这是因为对话框可以有很多种,包括各种各样的小部件排列和组合。

一旦将所有的小部件放在对话框的 GUI 上,就需要在该对话框上设置一个顶层布局。为此,您必须调用对话框对象上的.setLayout(),就像您对任何其他小部件所做的那样。

这里有一个对话框风格的应用程序,展示了如何设置一个QDialog对象的顶层布局:

 1import sys
 2
 3from PyQt5.QtWidgets import (
 4    QApplication,
 5    QDialog,
 6    QDialogButtonBox,
 7    QFormLayout,
 8    QLineEdit,
 9    QVBoxLayout,
10)
11
12class Dialog(QDialog):
13    def __init__(self):
14        super().__init__()
15        self.setWindowTitle("QDialog's Top-Level Layout Example")
16        dlgLayout = QVBoxLayout() 17        # Create a form layout and add widgets
18        formLayout = QFormLayout() 19        formLayout.addRow("Name:", QLineEdit()) 20        formLayout.addRow("Job:", QLineEdit()) 21        formLayout.addRow("Email:", QLineEdit()) 22        # Add a button box
23        btnBox = QDialogButtonBox()
24        btnBox.setStandardButtons(
25            QDialogButtonBox.Ok | QDialogButtonBox.Cancel
26        )
27        # Set the layout on the dialog
28        dlgLayout.addLayout(formLayout) 29        dlgLayout.addWidget(btnBox) 30        self.setLayout(dlgLayout) 31
32if __name__ == "__main__":
33    app = QApplication(sys.argv)
34    dlg = Dialog()
35    dlg.show()
36    sys.exit(app.exec_())

在这种情况下,应用程序的窗口继承自QDialog,所以你有一个对话框风格的应用程序。在第 16 行,您创建了将用作对话框顶层布局的布局。在第 18 到 21 行,您创建了一个表单布局来安排表单中的一些小部件。

在第 24 行,你添加一个 QDialogButtonBox 对象。您将经常使用QDialogButtonBox来处理对话框上的按钮。在这个例子中,您使用了两个按钮,一个确定按钮和一个取消按钮。这些按钮没有任何功能,它们只是为了让对话框更真实。

一旦所有的小部件和布局就绪,就可以将它们添加到顶层布局中。这就是你在第 28 和 29 行所做的。最后一步,在第 30 行,使用.setLayout()将顶层布局设置为对话框的布局。

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

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为所有对话框设置顶层布局是一个最佳实践。这确保了当用户调整底层窗口大小时,对话框的 GUI 行为一致。否则,在用户看来,你的对话框会显得杂乱无章,未经修饰。

管理 PyQt 布局中的空间

当使用 PyQt 的布局管理器在窗口或表单上排列小部件时,管理空间——空白空间、小部件之间的空间等等——是一个常见的问题。能够管理这个空间是一项重要的技能。

在内部,布局使用以下一些小部件属性来管理窗口上的可用空间:

布局使用这些属性自动定位和调整小部件的大小,根据可用空间为每个小部件分配给定的空间量。这确保了小部件被一致地排列并保持可用。

在接下来的三节中,您将了解不同类型的布局如何管理 PyQt 中的空间。

管理盒子布局中的空间

盒子布局在小部件之间分配可用空间方面做得很好。然而,有时它们的默认行为是不够的,您需要手动处理可用空间。为了在这种情况下帮助你,PyQt 提供了 QSpacerItem 。这个类允许你添加空白空间(或者空框)到一个框布局中。

正常情况下不需要直接使用QSpacerItem。相反,您可以在框布局对象上调用以下一些方法:

  • .addSpacing(i) 在布局中增加一个固定大小i的不可拉伸空间(或空框)。i必须是一个整数,以像素表示空间的大小。

  • .addStretch(i) 为盒子布局添加最小尺寸为0的可拉伸空间和拉伸系数ii必须是整数。

  • .insertSpacing(index, size)index位置插入一个不可拉伸的空格,大小size。如果index为负,则在方框布局的末尾添加空格。

  • insertStretch(index, stretch)index位置插入一个可拉伸的空格,最小尺寸0,拉伸系数stretch。如果index是负数,那么空格被添加到方框布局的末尾。

当用户调整底层窗口大小时,可拉伸的间隔条会膨胀或收缩以填充空白空间。不可拉伸的垫片将保持相同的尺寸,不管底层窗户的尺寸如何变化。

回到如何使用垂直布局的例子,并再次运行该应用程序。如果你拉下窗口的边框,你会注意到越往下,按钮之间出现的空间越大:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这是因为布局通过自动扩展其框来处理新的可用空间。您可以通过在布局的末尾添加一个可拉伸的QSpacerItem对象来改变这种行为。

在您的示例代码中,更新Window的初始化器,如下所示:

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"))
 layout.addStretch()        # Set the layout on the application's window
        self.setLayout(layout)

在突出显示的行中,通过调用布局上的.addStretch()将可拉伸的QSpacerItem对象添加到布局的末尾。如果您再次运行该应用程序,您将获得以下行为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在,所有额外的空间都自动分配给布局底部的可拉伸的QSpacerItem对象,而不会影响其余小部件的位置或大小。您可以使用这种和其他空间管理技术来使您的 GUI 应用程序看起来更好、更完美。

Remove ads

管理网格和表单布局中的空间

网格和表单布局以不同的方式处理可用空间。在这些类型的布局中,您只能处理小部件之间的垂直和水平空间。这些布局提供了三种管理这些空间的方法:

  1. setSpacing(spacing) 将小工具之间的垂直和水平间距设置为spacing
  2. setVerticalSpacing(spacing) 仅将布局中微件之间的垂直间距设置为spacing
  3. setHorizontalSpacing(spacing) 仅将布局中部件之间的水平间距设置为spacing

在所有情况下,spacing是表示像素的整数。现在回到如何创建一个表单布局并像这样更新Window的初始化器的例子:

class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QFormLayout Example")
        self.resize(270, 110)
        # Create a QHBoxLayout instance
        layout = QFormLayout()
        # Add widgets to the layout
 layout.setVerticalSpacing(30)        layout.addRow("Name:", QLineEdit())
        layout.addRow("Job:", QLineEdit())
        emailLabel = QLabel("Email:")
        layout.addRow(emailLabel, QLineEdit())
        # Set the layout on the application's window
        self.setLayout(layout)

在突出显示的行中,您将小部件之间的垂直间距设置为30像素。如果您再次运行该应用程序,您将看到以下窗口:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在小部件的行之间有了更多的空间。您还可以尝试通过添加一些垂直或水平空间来修改如何使用网格布局的示例,以便了解所有这些间距机制是如何工作的。

结论

创建高质量的 GUI 应用程序需要将所有的图形组件以一种连贯和完美的方式排列。在 PyQt 中,一种有效的方法是使用 PyQt 的布局管理器,它提供了一种用户友好且高效的方法来完成这项任务。

在本教程中,您已经学习了:

  • 在 GUI 上正确布局小部件的好处是什么
  • 如何使用 PyQt 内置的布局管理器以编程方式排列小部件
  • 哪个布局管理器用于您的特定用例
  • 如何在 PyQt 中布局主窗口式对话框式应用

有了这些知识,您将能够使用 PyQt 的内置布局创建好看且专业的 GUI 应用程序。

立即观看本教程有真实 Python 团队创建的相关视频课程。和书面教程一起看,加深你的理解: 为 GUI 应用程序创建 PyQt 布局***********

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐