Learn X in Y minutes 仓库中的 PyQt 入门指南:用 Python 构建跨平台 GUI 应用
Learn X in Y minutes 仓库中的 PyQt 入门指南:用 Python 构建跨平台 GUI 应用
本指南以 learnxinyminutes-docs 仓库中的法语文档 fr/pyqt.md 为主体,系统讲解如何用 PyQt 从零构建桌面 GUI 应用。你将掌握 Qt 框架的核心对象模型(QApplication、QWidget、QLabel、QPushButton、QDialog)、事件循环的启动方式,以及信号与槽(signal/slot)机制和模态对话框的实战用法,足以写出第一个可运行的窗口程序和带交互的弹窗程序。
文档定位:以代码为文档的 PyQt 快速入门
learnxinyminutes-docs 仓库的核心理念是 "Code documentation written as code!"(代码即文档),即把每一种编程语言或框架的入门教程写成带完整注释的可运行代码,边读边跑。README.md 明确欢迎任何形式的贡献,从最细微的拼写错误到全新的文章,且所有语言版本的翻译都受到鼓励。PyQt 教程正是这一理念的典型代表:仓库根目录的 pyqt.md 是英文原版,其 frontmatter 中标注了 filename: learnpyqt.py,意味着整篇教程最终应保存为一个可直接执行的 Python 脚本。
法语版 fr/pyqt.md 是英文原版经译者 DevHugo 翻译的忠实改编,两个版本的核心代码与注释结构完全一致。据文档 self 说明,该教程内容改编自 Aleksey Kholovchuk 编写的 C++ 版 Qt 简介,用 PyQt 重新实现了其中部分相同功能——这正是学习框架的好方法:对照同一功能的 C++ 与 Python 实现,理解 API 背后的设计意图。除法语外,该教程还拥有 de/pyqt.md、es/pyqt.md、id/pyqt.md、it/pyqt.md、pt-br/pyqt.md、ru/pyqt.md、zh-cn/pyqt.md 等多个语言版本,是仓库中覆盖面最广的教程之一。
Qt 框架背景:跨平台 GUI 的力量
文档开篇即点明 Qt 的定位:Qt 是一个广为人知的跨平台软件开发框架,编写出的软件可以在多种软件与硬件平台上运行,几乎不需要或只需要很少的代码改动,同时保留原生应用应有的性能与速度。尽管 Qt 最初是用 C++ 编写的,但通过 PyQt 这一 Python 绑定,开发者可以完全用 Python 语法驱动 Qt 的全部能力,在保持开发效率的同时获得接近原生应用的运行体验。
理解 PyQt 编程,首先要建立两个基本概念:
- 对象树与父子关系:Qt 中的控件(widget)通过构造函数传入父控件建立隶属关系。父控件负责管理子控件的生命周期,子控件随父控件一起显示、销毁。本教程的代码大量运用了这一模式。
- 事件循环(event loop):GUI 程序不是顺序执行完就退出的,而是进入一个不断处理用户输入、重绘、定时器等事件的主循环,直到程序被要求退出。
第一个程序:Hello World 窗口
文档给出的第一个示例展示了 PyQt 应用的最小完整骨架:创建应用对象 → 创建控件 → 设置内容与布局 → 显示 → 启动事件循环。完整代码如下:
import sys
from PyQt4 import QtGui
def window():
# 创建应用对象
app = QtGui.QApplication(sys.argv)
# 创建一个 widget,作为 label 的父控件
w = QtGui.QWidget()
# 在 widget 中添加一个 label 子控件
b = QtGui.QLabel(w)
# 设置 label 的文字
b.setText("Hello World!")
# 设置 widget 的尺寸和位置
w.setGeometry(100, 100, 200, 50)
b.move(50, 20)
# 设置窗口的标题
w.setWindowTitle("PyQt")
# 显示 widget 及其所有子控件
w.show()
# 启动事件循环并阻塞,直到应用程序退出
sys.exit(app.exec_())
if __name__ == '__main__':
window()
逐行拆解这段代码,每个步骤都对应 PyQt 编程的核心知识点:
app = QtGui.QApplication(sys.argv):每个 PyQt 程序都必须且只能有一个QApplication实例。它负责初始化 Qt 运行环境、管理事件循环,并接收命令行参数sys.argv——这也是 Qt 程序可以原生解析命令行选项(如-style、-display等)的基础。w = QtGui.QWidget():QWidget是所有用户界面对象的基类。这里它作为顶层窗口容器,后续的 label 将作为其子控件挂载。不传父控件参数时,QWidget即成为独立窗口。b = QtGui.QLabel(w):创建QLabel文本标签,并通过构造函数参数w将其挂到窗口w下,建立父子关系。这正是前面提到的对象树机制的第一次应用。b.setText("Hello World!"):为标签设置显示文本。也可以直接在构造时传入文本。w.setGeometry(100, 100, 200, 50):以(x, y, width, height)四个参数一次性设置窗口在屏幕上的位置(左上角坐标 100, 100)与尺寸(宽 200、高 50)。b.move(50, 20):将标签移动到相对于其父控件w的 (50, 20) 位置。父控件坐标系是子控件定位的基准。w.setWindowTitle("PyQt"):设置窗口标题栏文字。w.show():让窗口及其所有子控件可见。注意 Qt 中控件默认是隐藏的,必须显式调用show()才会显示。sys.exit(app.exec_()):exec_()启动 Qt 事件循环并阻塞当前线程,直到窗口关闭或程序调用退出。sys.exit()把exec_()的返回值作为进程退出码传给操作系统,保证程序干净退出。
最后用 if __name__ == '__main__': 保护入口,确保只有直接运行该脚本时才启动 window(),被其他模块导入时不会误触发 GUI。
运行后,屏幕上会出现一个标题为 "PyQt" 的 200×50 窗口,其中显示 "Hello World!" 文本。
信号与槽机制:让界面响应点击
静态显示文本只是起点。文档的第二段示例引入交互能力,演示按钮点击如何触发函数调用。这里用到了 Qt 最核心的**信号与槽(signals and slots)**机制:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
def window():
app = QApplication(sys.argv)
w = QWidget()
# 创建一个按钮并添加到 widget 控件 w
b = QPushButton(w)
b.setText("Press me")
b.move(50, 50)
# 当按钮 b 被点击时调用 showdialog 函数
# 注意函数调用时没有 "()",这样函数就能以对象的方式传入,
# 而非传入执行它所得到的返回值
b.clicked.connect(showdialog)
w.setWindowTitle("PyQt Dialog")
w.show()
sys.exit(app.exec_())
# 对话框窗口创建函数:弹出一个等待点击的对话框,点击后退出程序
def showdialog():
d = QDialog()
b1 = QPushButton("ok", d)
b1.move(50, 50)
d.setWindowTitle("Dialog")
# 设置模态:弹出时阻塞程序并屏蔽父窗口
d.setWindowModality(Qt.ApplicationModal)
# 当按钮被点击时整个进程将会结束
b1.clicked.connect(sys.exit)
d.exec_()
if __name__ == '__main__':
window()
这段代码引入了三个新元素:
QPushButton:可点击的按钮控件
b = QPushButton(w) 创建按钮并挂到窗口 w 上,b.setText("Press me") 设置按钮文字,b.move(50, 50) 将其放在窗口内指定位置。注意这里使用了 from PyQt4.QtGui import * 的通配导入,因此可以直接写 QPushButton 而不带模块前缀。
信号与槽:clicked.connect(showdialog)
b.clicked.connect(showdialog) 是 Qt 信号机制最直观的用法:按钮被点击时,clicked 信号被发射,与之连接的槽函数 showdialog 被调用。
文档在注释中特别强调了关键细节:connect 时传入的是函数对象本身,而不是调用它的结果。showdialog 后面没有括号,意味着此时并没有执行函数,只是把函数引用注册为信号的处理器;只有点击发生时,Qt 才会调用它。如果写成 b.clicked.connect(showdialog()),就会在连接阶段立即执行函数并把返回值传给 connect,结果完全不同。同一机制也适用于把内置函数当作槽,比如 b1.clicked.connect(sys.exit) 直接将 sys.exit 函数对象连接到按钮点击信号,点击即退出整个进程。
QDialog 与模态对话框
showdialog() 函数内部创建了一个 QDialog 对话框对象:
d = QDialog():对话框是用于短期交互(确认、输入、提示)的专用顶层窗口。b1 = QPushButton("ok", d):在构造按钮的同时传入父控件d,将按钮挂到对话框内,等价于先QPushButton(d)再setText("ok")。d.setWindowModality(Qt.ApplicationModal):这是模态(modality)设置的关键行。Qt.ApplicationModal告诉对话框:在它处于活动状态期间,阻塞整个应用程序中所有其他窗口的输入,用户必须先处理该对话框才能回到主窗口。这正是"确认决策或提供信息"场景所需的交互约束。Qt.ApplicationModal属于QtCore模块中的Qt命名空间常量,因此示例文件顶部同时导入了from PyQt4.QtCore import *。d.exec_():以模态方式启动对话框的本地事件循环。它会阻塞,直到对话框被关闭。
组合起来的完整流程是:点击主窗口的 "Press me" → 弹出模态对话框 "Dialog" → 点击其中的 "ok" 按钮 → 触发 sys.exit 槽 → 整个进程结束。读者可以自行把 b1.clicked.connect(sys.exit) 改为 b1.clicked.connect(d.close) 或 b1.clicked.connect(d.accept) 来体验"只关对话框、不退出程序"的差异。
PyQt 事件循环与 exec_ 方法名的由来
两个示例中反复出现 exec_(),其命名有历史原因:PyQt4 对应 Python 2 时代,exec 是 Python 的保留关键字,因此 PyQt 将 Qt 的 exec() 方法加下划线命名为 exec_()。无论是 app.exec_() 还是 d.exec_(),其本质都是启动一个阻塞式事件循环:主窗口的事件循环负责整个应用,对话框的 exec_() 则运行一个嵌套的本地循环,二者都返回一个整数结果,通常用 sys.exit() 包装以设置进程退出码。
代码要点速查
| API | 所属模块 | 作用 |
|---|---|---|
QApplication(sys.argv) |
PyQt4.QtGui |
创建应用实例,每个 PyQt 程序必需且唯一 |
QWidget() |
PyQt4.QtGui |
所有控件的基类,此处作为顶层窗口容器 |
QLabel(text, parent) |
PyQt4.QtGui |
显示文本标签 |
QPushButton(text, parent) |
PyQt4.QtGui |
可点击按钮 |
QDialog() |
PyQt4.QtGui |
用于短期交互的对话框窗口 |
setGeometry(x, y, w, h) |
— | 一次性设置窗口位置与尺寸 |
move(x, y) |
— | 相对父控件移动子控件 |
setWindowTitle(title) |
— | 设置窗口标题栏 |
setText(text) |
— | 设置控件显示文本 |
show() |
— | 显示控件及其子控件 |
exec_() |
— | 启动阻塞式事件循环 |
clicked.connect(slot) |
PyQt4.QtCore |
信号与槽连接,传入函数对象而非调用结果 |
setWindowModality(Qt.ApplicationModal) |
PyQt4.QtCore |
设置模态,阻塞父窗口输入 |
Qt.ApplicationModal |
PyQt4.QtCore |
应用级模态常量 |
关于 PyQt4 的版本说明
需要明确的是,本仓库中的 PyQt 教程(含法语版 fr/pyqt.md 与英文原版 pyqt.md)针对的是 PyQt4 时代的 API:控件类位于 PyQt4.QtGui 模块中,事件循环方法写作 exec_()。如果读者在较新的 PyQt 版本(PyQt5 及以上)中运行示例,模块划分已经发生变化——控件类迁移到了 QtWidgets 模块,exec_() 也回归为 exec()——直接照搬本教程的 import 语句会导入失败。不过本教程传授的对象树、信号与槽、模态对话框、事件循环等核心概念在 Qt 各版本中一脉相承,掌握这些思想后迁移到新版本只是改 import 与个别方法名的机械工作。以本仓库文档为准时,请将其作为 PyQt4 概念学习的入门材料来使用。
在仓库中继续深入与参与贡献
如果想继续学习,仓库提供了完整的对照资源:
- 英文原版教程 pyqt.md:frontmatter 标注了
category: framework,并建议将示例保存为learnpyqt.py文件; - 中文译本 zh-cn/pyqt.md:其注释在信号槽连接处补充了"更多关于 pyqt 函数调用、传参等的内容见 pyqt 的信号机制"的指引,并解释了
app.exec_()启动事件循环并阻塞的细节; - 其他语言版本:de/pyqt.md、es/pyqt.md、id/pyqt.md、it/pyqt.md、pt-br/pyqt.md、ru/pyqt.md。
在动手运行示例时,建议先建立虚拟环境并安装对应版本的 PyQt,再按文档结构把 window() 与 showdialog() 函数、if __name__ == '__main__' 入口完整保存为脚本执行,即可直观验证事件循环、信号槽与模态对话框的实际行为。若你希望为仓库贡献新的语言版本或改进注释,README.md 与 CONTRIBUTING.md 中说明了贡献流程与格式规范——每个教程文件都包含 contributors 与 translators 元信息字段,翻译者的署名会在文档头部被正式记录。