PyQt 快速入门:Python GUI 界面设计 – wiki基地


PyQt 快速入门:Python GUI 界面设计

引言

在现代软件开发中,图形用户界面(GUI,Graphical User Interface)扮演着至关重要的角色。一个友好的图形界面能够极大地提升用户体验,使得复杂的程序更容易被普通用户接受和使用。对于 Python 开发者来说,有多种库可以选择来创建 GUI 应用程序,其中 PyQt 是一个非常强大且流行的选择。

PyQt 是 Python 对 Qt 应用开发框架的绑定。Qt 是一个由 Trolltech(后被 Digia、Qt Company 收购)开发的跨平台 C++ 库,广泛用于开发 GUI 应用程序,也可以用于创建非 GUI 程序,如命令行工具和服务器。由于 Qt 本身的高质量、丰富的功能和跨平台特性,PyQt 也继承了这些优点,使得 Python 开发者能够快速、高效地构建出美观、功能强大的 GUI 应用程序。

本文将带领大家从零开始,逐步学习如何使用 PyQt 进行 GUI 界面设计。我们将涵盖环境搭建、第一个 PyQt 应用的创建、常用控件的使用、布局管理、信号与槽机制,以及如何利用 Qt Designer 这一可视化工具加速开发。无论你是一名 Python 初学者,还是希望扩展技能树的老手,本文都将为你提供一个坚实的基础。

第一章:初识 PyQt 与 GUI 基础

什么是 GUI?

GUI,即图形用户界面,是用户与计算机程序交互的一种方式,它使用图形元素(如窗口、按钮、图标、菜单、文本框等)来替代传统的命令行输入方式。通过 GUI,用户可以通过点击、拖拽、输入等直观的操作来控制程序。

什么是 Qt?

Qt 是一个功能丰富的跨平台应用开发框架,使用 C++ 编写。它不仅提供了强大的 GUI 工具包,还包含了网络、数据库、XML 解析、多线程等众多模块。Qt 最早于 1995 年发布,因其“一次编写,到处运行”(Write Once, Deploy Anywhere)的特性而广受欢迎,广泛应用于桌面、嵌入式和移动设备开发。

什么是 PyQt?

PyQt 是 Python 语言与 Qt 库的集成。它允许 Python 程序员利用 Qt 库的强大功能来开发应用程序。PyQt 是一个商业产品,但它也提供了一个开源的 GPL 许可证版本(对于开发开源软件免费)。与 PyQt 类似的还有一个项目叫 PySide,它是 Qt 官方提供的 Python 绑定,使用 LGPL 许可证,对于商业开发更友好。在功能和用法上,PyQt 和 PySide 非常相似,本文将主要以 PyQt5 为例进行讲解(PyQt6 是最新版本,语法略有差异但核心概念一致)。

为什么选择 PyQt?

  • 跨平台性: 使用 PyQt 开发的应用程序可以在 Windows、macOS、Linux 等主流操作系统上运行,无需修改代码。
  • 功能丰富: PyQt 提供了 Qt 库的几乎所有功能,包括各种控件、布局管理器、图形视图、网络通信、数据库支持等。
  • 成熟稳定: Qt 是一个经过多年发展和验证的框架,稳定性和性能都非常好。
  • 强大的社区支持: Qt 和 PyQt 都有庞大的开发者社区,遇到问题容易找到解决方案。
  • 集成开发工具: PyQt 通常与 Qt Designer 配合使用,可以通过可视化界面快速设计 UI 布局,提高开发效率。
  • Python 的易用性: 结合 Python 语言的简洁和易读性,使得 PyQt 开发更加便捷。

与 Python 标准库中的 Tkinter 相比,PyQt 提供了更现代化、更丰富的控件和更强大的功能;与 Kivy 等其他第三方 GUI 库相比,PyQt 尤其在开发传统的桌面应用方面具有优势。

第二章:环境搭建与安装

在开始使用 PyQt 之前,你需要确保你的系统上已经安装了 Python。推荐使用 Python 3.6 或更高版本。

安装 PyQt 非常简单,通常使用 pip 包管理器即可完成。

使用 pip 安装 PyQt

打开你的终端或命令提示符,执行以下命令来安装 PyQt5:

bash
pip install PyQt5

如果你希望安装最新版本的 PyQt6,可以使用:

bash
pip install PyQt6

本文主要以 PyQt5 为例,因此建议安装 PyQt5 进行学习。安装过程可能需要一些时间,pip 会自动下载并安装 PyQt5 及其依赖项。

安装 Qt Designer 及其他工具

为了使用可视化界面设计工具 Qt Designer,你需要安装 pyqt5-tools 包(或 pyqt6-tools for PyQt6)。

bash
pip install pyqt5-tools

安装完成后,你可以在 Python 环境的 Scripts (Windows) 或 bin (macOS/Linux) 目录下找到 designer.exe (Windows) 或 designer (macOS/Linux) 可执行文件。这个工具就是 Qt Designer,我们稍后会详细介绍它的用法。

验证安装

安装完成后,可以通过简单的 Python 代码来验证 PyQt 是否安装成功。打开 Python 交互式环境或创建一个 Python 文件,输入以下代码:

python
import PyQt5.QtWidgets
print("PyQt5 安装成功!")

如果运行没有报错并输出了“PyQt5 安装成功!”,那么恭喜你,PyQt 环境已经搭建完毕。

第三章:第一个 PyQt 应用:一个简单的窗口

学习任何 GUI 库的第一步通常是创建一个空白窗口。在 PyQt 中,一个最基本的 GUI 应用需要以下几个核心组件:

  1. QApplication 对象: 应用程序的入口,负责事件循环、窗口管理等。每个 PyQt 应用都必须有且只有一个 QApplication 实例。
  2. 主窗口或其他控件: GUI 界面的主体,通常是一个继承自 QWidgetQMainWindowQDialog 的类。
  3. 显示窗口: 调用窗口对象的 show() 方法使其可见。
  4. 进入应用程序事件循环: 调用 QApplication 对象的 exec_() 方法(注意是 exec_,因为 exec 是 Python 的关键字),这会启动应用程序的事件循环,使程序保持运行状态,响应用户的交互。

下面是一个创建最简单 PyQt 窗口的完整代码:

“`python
import sys
from PyQt5.QtWidgets import QApplication, QWidget

1. 创建 QApplication 类的实例

QApplication 是所有 Qt 应用程序的核心,用于管理应用程序的控制流程。

sys.argv 是命令行参数列表,大多数情况下我们不需要处理它,但传入它是标准的做法。

app = QApplication(sys.argv)

2. 创建一个 QWidget 对象

QWidget 是所有用户界面对象的基类。QWidget 窗口没有父级时,被当做顶级窗口。

一个 QWidget 可以是任何用户界面元素,例如按钮、标签、文本框等。

如果没有指定父级,它就是一个独立的窗口。

window = QWidget()

3. 设置窗口的属性 (可选)

window.setWindowTitle(‘我的第一个 PyQt 窗口’) # 设置窗口标题
window.setGeometry(100, 100, 400, 300) # 设置窗口位置和大小 (x, y, width, height)

4. 显示窗口

window.show()

5. 进入应用程序事件循环

app.exec_() 方法启动应用程序的事件循环。

程序会一直在这里运行,直到用户关闭窗口或调用 app.quit()。

事件循环接收事件(如鼠标点击、键盘输入、窗口resize等),并把它们分发给相应的控件。

当事件循环退出时,sys.exit() 函数用于确保程序干净地退出。

sys.exit(app.exec_())
“`

代码解释:

  • import sys: 导入 sys 模块,用于访问命令行参数和控制程序退出。
  • from PyQt5.QtWidgets import QApplication, QWidget: 从 PyQt5 的 QtWidgets 模块中导入 QApplicationQWidget 类。QtWidgets 模块包含了所有用户界面相关的类。
  • app = QApplication(sys.argv): 创建 QApplication 实例。sys.argv 传递命令行参数给 Qt 应用程序。
  • window = QWidget(): 创建一个 QWidget 实例。作为一个顶级窗口,它会显示为一个空白的窗口。
  • window.setWindowTitle('...'): 设置窗口的标题栏文本。
  • window.setGeometry(100, 100, 400, 300): 设置窗口的位置和大小。100, 100 是窗口左上角在屏幕上的坐标(从左上角开始),400, 300 是窗口的宽度和高度。
  • window.show(): 显示窗口。默认情况下,窗口是隐藏的。
  • sys.exit(app.exec_()): 启动应用程序的事件循环并等待退出。当窗口关闭时,exec_() 方法会返回一个退出码,sys.exit() 使用这个退出码退出程序。

保存这段代码为 .py 文件(例如 first_app.py),然后在终端中运行 python first_app.py,你就会看到一个标题为“我的第一个 PyQt 窗口”的空白窗口出现在屏幕上。关闭窗口后,程序就会终止。

这是所有 PyQt 应用程序的基础框架,后续我们将在这个框架中添加各种控件和功能。

第四章:常用控件 (Widgets) 介绍与使用

控件(Widget)是构建 GUI 界面的基本组成单元,它们可以是按钮、标签、文本框等。PyQt 提供了大量内置控件,足以满足大多数界面设计的需求。本章我们将介绍几个最常用的控件及其基本用法。

我们将继续在上一个例子创建的窗口中添加这些控件。为了更好地组织控件,我们需要引入布局管理的概念,但这将在下一章详细介绍。在本章的示例中,我们暂时使用 setGeometry() 方法手动定位控件,但这在实际开发中并不推荐,因为它不易维护且不能很好地处理窗口大小调整。

QLabel (标签)

QLabel 用于显示文本或图像。

“`python
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel

app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle(‘QLabel 示例’)
window.setGeometry(100, 100, 400, 300)

创建一个 QLabel 控件

label = QLabel(‘Hello, PyQt!’, window) # 第一个参数是显示的文本,第二个参数指定父控件 (这里是 window)
label.setGeometry(150, 100, 100, 30) # 手动设置位置和大小

window.show()
sys.exit(app.exec_())
“`

解释:

  • QLabel('Hello, PyQt!', window): 创建一个 QLabel 实例,文本内容为 “Hello, PyQt!”,并将其父控件设置为 window。将控件的父控件设置为窗口,意味着这个控件将显示在窗口内部,并且当窗口被销毁时,子控件也会被销毁。
  • label.setGeometry(150, 100, 100, 30): 手动设置 QLabel 的位置 (150, 100) 和大小 (100×30)。

运行代码,窗口中会出现“Hello, PyQt!”字样的文本。

QPushButton (按钮)

QPushButton 是最常见的交互控件,用于触发动作。

“`python
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton

app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle(‘QPushButton 示例’)
window.setGeometry(100, 100, 400, 300)

创建一个 QLabel

label = QLabel(‘等待点击按钮…’, window)
label.setGeometry(150, 100, 150, 30)

创建一个 QPushButton

button = QPushButton(‘点我!’, window) # 按钮文本和父控件
button.setGeometry(150, 150, 100, 30) # 手动设置位置和大小

我们将在下一章介绍如何让按钮真正做出反应 (使用信号与槽)

window.show()
sys.exit(app.exec_())
“`

解释:

  • QPushButton('点我!', window): 创建一个按钮,文本为“点我!”,父控件为 window
  • button.setGeometry(150, 150, 100, 30): 手动设置按钮的位置和大小。

运行代码,窗口中会显示文本和一个按钮。点击按钮目前不会发生任何事情,因为我们还没有关联它的点击事件。

QLineEdit (单行文本输入框)

QLineEdit 用于接收用户的单行文本输入。

“`python
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QLineEdit

app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle(‘QLineEdit 示例’)
window.setGeometry(100, 100, 400, 300)

创建一个 QLabel 提示用户输入

label_prompt = QLabel(‘请输入你的名字:’, window)
label_prompt.setGeometry(50, 50, 100, 30)

创建一个 QLineEdit 用于输入

line_edit = QLineEdit(window)
line_edit.setGeometry(150, 50, 200, 30)

(可选) 设置默认文本或占位文本

line_edit.setText(“默认文本”)
line_edit.setPlaceholderText(“输入文本…”)

创建一个 QLabel 用于显示结果

label_result = QLabel(‘你输入了:’, window)
label_result.setGeometry(50, 100, 300, 30)

我们将在下一章学习如何获取 line_edit 的文本并更新 label_result

window.show()
sys.exit(app.exec_())
“`

解释:

  • QLineEdit(window): 创建一个单行文本输入框,父控件为 window
  • line_edit.setText(...): 设置文本框的当前文本。
  • line_edit.setPlaceholderText(...): 设置当文本框为空时显示的提示文本(占位符)。

运行代码,窗口中会出现一个输入框和两个标签。你可以在输入框中输入文本。

QTextEdit (多行文本输入框)

QTextEdit 用于接收和显示多行富文本(带格式的文本)。

“`python
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QTextEdit

app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle(‘QTextEdit 示例’)
window.setGeometry(100, 100, 400, 300)

创建一个 QLabel

label = QLabel(‘请输入多行文本:’, window)
label.setGeometry(50, 50, 100, 30)

创建一个 QTextEdit

text_edit = QTextEdit(window)
text_edit.setGeometry(50, 90, 300, 150) # 设置更大的尺寸以容纳多行文本

(可选) 设置默认文本

text_edit.setText(“这是默认的多行文本内容。\n可以在这里输入更多内容。”)

window.show()
sys.exit(app.exec_())
“`

解释:

  • QTextEdit(window): 创建一个多行文本输入框,父控件为 window
  • text_edit.setText(...): 设置文本框的当前文本,可以使用 \n 实现换行。

运行代码,你会看到一个可以输入多行文本的区域。

QCheckBox 和 QRadioButton

  • QCheckBox (复选框):允许用户选择或取消选择一个选项,选项之间通常是独立的。
  • QRadioButton (单选按钮):允许用户在一组互斥的选项中选择一个。通常需要将一组 QRadioButton 放在一个 QButtonGroup 中(可选,但推荐用于管理状态)或一个父容器(如 QGroupBox)中以实现互斥功能。

“`python
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QCheckBox, QRadioButton, QVBoxLayout, QGroupBox

app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle(‘CheckBox & RadioButton 示例’)
window.setGeometry(100, 100, 400, 300)

由于手动定位控件太麻烦,我们先简单使用布局来组织,具体布局内容下一章详述

layout = QVBoxLayout() # 创建一个垂直布局
window.setLayout(layout) # 将布局设置到窗口上

CheckBox 示例

checkbox1 = QCheckBox(‘选项 A’)
checkbox2 = QCheckBox(‘选项 B’)
checkbox1.setChecked(True) # 设置为默认选中

RadioButton 示例

将 RadioButton 放在一个 GroupBox 中,可以使它们自动互斥

radio_group_box = QGroupBox(“请选择一个水果”)
radio_layout = QVBoxLayout()
radio1 = QRadioButton(“苹果”)
radio2 = QRadioButton(“香蕉”)
radio3 = QRadioButton(“橙子”)
radio1.setChecked(True) # 设置默认选中
radio_layout.addWidget(radio1)
radio_layout.addWidget(radio2)
radio_layout.addWidget(radio3)
radio_group_box.setLayout(radio_layout)

将控件和 GroupBox 添加到主布局

layout.addWidget(checkbox1)
layout.addWidget(checkbox2)
layout.addWidget(radio_group_box)

我们将在下一章学习如何获取它们的状态

window.show()
sys.exit(app.exec_())
“`

解释:

  • QCheckBox('选项 A'): 创建一个复选框,文本为“选项 A”。
  • checkbox1.setChecked(True): 设置复选框为选中状态。
  • QRadioButton("苹果"): 创建一个单选按钮,文本为“苹果”。
  • QGroupBox("请选择一个水果"): 创建一个分组框,通常用于对一组相关的控件进行逻辑上的分组和视觉上的框定。
  • QVBoxLayout(): 创建一个垂直布局管理器。
  • radio_group_box.setLayout(radio_layout): 将垂直布局应用到分组框。
  • layout.addWidget(...): 将控件或子布局添加到主布局中。
  • window.setLayout(layout): 将主布局应用到窗口。

通过这个例子,你已经初步接触了一些常用控件。然而,手动使用 setGeometry 定位控件效率低下且不利于维护。下一章我们将学习如何使用 PyQt 的布局管理器来更优雅地组织界面。

第五章:布局管理 (Layouts)

在复杂的 GUI 界面中,控件的数量很多,而且窗口大小可能会被用户调整。手动计算并设置每个控件的位置和大小是不可行的。PyQt 的布局管理器(Layouts)就是用来解决这个问题的。布局管理器负责自动安排控件在容器(如窗口或分组框)中的位置和大小,并根据窗口大小的变化自动调整控件的布局。

PyQt 提供了多种布局管理器:

  • QVBoxLayout (垂直布局): 将控件垂直排列。
  • QHBoxLayout (水平布局): 将控件水平排列。
  • QGridLayout (网格布局): 将控件放置在由行和列组成的网格中。
  • QFormLayout (表单布局): 用于创建双列表单,通常左侧是标签,右侧是对应的输入控件。
  • QStackedLayout (堆叠布局): 一次只显示一个控件(像卡片叠在一起)。

一个容器(如 QWidgetQMainWindowQGroupBox 等)只能设置一个主布局。但布局管理器本身也可以包含其他布局管理器,通过嵌套布局,可以实现任意复杂的界面布局。

下面我们将以上一章的控件为例,使用布局管理器来重新组织它们。

使用布局管理器组织控件

“`python
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QPushButton,
QLineEdit, QTextEdit, QCheckBox, QRadioButton,
QVBoxLayout, QHBoxLayout, QGridLayout, QGroupBox) # 导入需要的类

app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle(‘布局管理示例’)
window.setGeometry(100, 100, 600, 400) # 稍微大一点的窗口

1. 创建一个主布局 (例如垂直布局)

main_layout = QVBoxLayout()
window.setLayout(main_layout) # 将主布局设置到窗口上

2. 添加一些控件到主布局

label_title = QLabel(‘欢迎来到布局示例!’)
main_layout.addWidget(label_title) # 将标签添加到主布局

3. 创建一个水平布局,并在其中放置输入框和按钮

input_layout = QHBoxLayout()
label_name = QLabel(‘名字:’)
line_edit_name = QLineEdit()
button_greet = QPushButton(‘问候’)

input_layout.addWidget(label_name) # 添加标签到水平布局
input_layout.addWidget(line_edit_name) # 添加输入框到水平布局
input_layout.addWidget(button_greet) # 添加按钮到水平布局

main_layout.addLayout(input_layout) # 将这个水平布局添加到主布局中

4. 添加一个多行文本框到主布局

text_edit_notes = QTextEdit()
main_layout.addWidget(text_edit_notes)

5. 添加 CheckBox 和 RadioButton (使用上一章 GroupBox 的例子)

radio_group_box = QGroupBox(“请选择一个水果”)
radio_layout = QVBoxLayout()
radio1 = QRadioButton(“苹果”)
radio2 = QRadioButton(“香蕉”)
radio3 = QRadioButton(“橙子”)
radio1.setChecked(True)
radio_layout.addWidget(radio1)
radio_layout.addWidget(radio2)
radio_layout.addWidget(radio3)
radio_group_box.setLayout(radio_layout)

main_layout.addWidget(radio_group_box) # 将分组框添加到主布局

6. 添加复选框 (直接添加到主布局下方)

checkbox1 = QCheckBox(‘同意条款’)
checkbox2 = QCheckBox(‘接收通知’)
main_layout.addWidget(checkbox1)
main_layout.addWidget(checkbox2)

7. (可选) 添加伸缩器 (Spacer) 使控件靠顶部对齐,底部留白

main_layout.addStretch(1) # 添加一个伸缩器,它会占据所有额外的垂直空间

window.show()
sys.exit(app.exec_())
“`

代码解释:

  • 我们创建了一个 QVBoxLayout 作为窗口的主布局,并通过 window.setLayout(main_layout) 将其应用到窗口。
  • main_layout.addWidget(label_title): 将 QLabel 控件添加到主布局中。
  • 我们又创建了一个 QHBoxLayout (input_layout) 来组织 QLabelQLineEditQPushButton,使得它们水平排列。
  • input_layout.addWidget(...): 将控件添加到 input_layout 中。
  • main_layout.addLayout(input_layout): 将整个 input_layout (作为一个整体) 添加到 main_layout 中。
  • main_layout.addWidget(text_edit_notes): 将 QTextEdit 添加到主布局中,它会出现在水平布局的下方。
  • 我们将 QGroupBox (其中包含了 RadioButtons) 也添加到了主布局。
  • 最后,将两个 CheckBox 添加到主布局。

运行这段代码,你会看到所有控件都根据布局管理器的规则自动排列。尝试调整窗口大小,你会发现控件的位置和大小也会相应地调整,这就是布局管理器的强大之处。

QGridLayout (网格布局) 示例

QGridLayout 允许你将控件放置在一个二维的网格中,通过指定行和列来定位控件。

“`python
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QPushButton, QGridLayout

app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle(‘网格布局示例’)
window.setGeometry(100, 100, 400, 200)

grid_layout = QGridLayout()
window.setLayout(grid_layout)

创建一些控件

label_name = QLabel(‘姓名:’)
line_edit_name = QLineEdit()

label_email = QLabel(‘邮箱:’)
line_edit_email = QLineEdit()

button_submit = QPushButton(‘提交’)

使用 addWidget(widget, row, column, rowSpan, columnSpan) 将控件添加到网格布局

row 和 column 是控件左上角所在的行和列索引 (从0开始)

rowSpan 和 columnSpan (可选) 指定控件跨越的行数和列数,默认是 1

grid_layout.addWidget(label_name, 0, 0) # 姓名标签在 (0, 0)
grid_layout.addWidget(line_edit_name, 0, 1) # 姓名输入框在 (0, 1)

grid_layout.addWidget(label_email, 1, 0) # 邮箱标签在 (1, 0)
grid_layout.addWidget(line_edit_email, 1, 1) # 邮箱输入框在 (1, 1)

提交按钮跨越一行,位于第 2 行,从第 0 列开始,跨越 2 列

grid_layout.addWidget(button_submit, 2, 0, 1, 2)

(可选) 设置列伸缩因子,例如让第二列填充可用空间

grid_layout.setColumnStretch(1, 1)

window.show()
sys.exit(app.exec_())
“`

解释:

  • QGridLayout(): 创建一个网格布局。
  • grid_layout.addWidget(widget, row, column, rowSpan, columnSpan): 将控件添加到网格的指定位置。rowcolumn 是从 0 开始的索引。rowSpancolumnSpan 默认是 1,如果设置为大于 1 的值,控件将跨越多行或多列。
  • grid_layout.setColumnStretch(1, 1): 设置第二列 (索引为 1) 的伸缩因子为 1。伸缩因子决定了当有额外空间时,各行/列如何分配这些空间。因子大的行/列会获得更多空间。这里设置为 1 意味着第二列会尽可能占据所有剩余的水平空间。

网格布局非常适合创建表单或类似表格的界面。

掌握布局管理是 PyQt 开发的关键一步,它能让你创建出具有响应式、自适应能力的复杂界面。

第六章:信号与槽 (Signals and Slots)

GUI 应用程序的核心在于响应用户的操作或系统事件。在 PyQt (以及 Qt) 中,这通过“信号与槽”(Signals and Slots)机制来实现。这是一种非常灵活且强大的事件处理机制。

  • 信号 (Signal): 当某个特定事件发生时,控件会发射(emit)一个信号。例如,一个按钮被点击时,它会发射 clicked 信号;一个文本框的文本改变时,它会发射 textChanged 信号。
  • 槽 (Slot): 槽是一个可以接收信号的函数或方法。当一个信号连接到某个槽时,每当该信号被发射,与之连接的槽就会被自动调用执行相应的代码。槽可以是任何 Python 可调用对象(函数、类方法、甚至是 lambda 表达式)。

通过将信号与槽连接起来,我们可以实现控件之间的交互和响应。

连接信号与槽

使用控件对象的信号属性的 connect() 方法来连接信号与槽:

python
signal_object.signal_name.connect(slot_function)

例如,将一个按钮的 clicked 信号连接到一个名为 on_button_click 的函数:

python
button.clicked.connect(on_button_click)

信号与槽示例:按钮点击改变文本

让我们回到第四章的按钮示例,现在我们将实现点击按钮后改变一个标签的文本。

“`python
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout

class MyWindow(QWidget):
def init(self):
super().init() # 调用父类 QWidget 的构造函数
self.initUI() # 调用自定义的界面初始化方法

def initUI(self):
    self.setWindowTitle('信号与槽示例')
    self.setGeometry(100, 100, 300, 200)

    # 创建布局
    layout = QVBoxLayout()
    self.setLayout(layout)

    # 创建控件
    self.label = QLabel('初始文本') # 使用 self 使 label 成为类的属性,方便在其他方法中访问
    self.button = QPushButton('改变文本')

    # 将控件添加到布局
    layout.addWidget(self.label)
    layout.addWidget(self.button)

    # 连接信号与槽
    # 当 button 的 clicked 信号发射时,调用 self.change_label_text 方法 (槽)
    self.button.clicked.connect(self.change_label_text)

# 槽方法 (槽可以接收信号传递的参数,clicked 信号默认不传递参数)
def change_label_text(self):
    print("按钮被点击了!") # 可以在控制台打印一些信息验证
    self.label.setText('文本已改变!') # 改变 label 的文本

应用程序入口

if name == ‘main‘:
app = QApplication(sys.argv)
# 创建自定义窗口类的实例
main_window = MyWindow()
main_window.show() # 显示窗口
sys.exit(app.exec_()) # 进入事件循环
“`

代码解释:

  • 我们将窗口创建封装在一个继承自 QWidgetMyWindow 类中。这是一种更好的组织 GUI 代码的方式,将界面元素和逻辑放在一个类中。
  • MyWindow 类的 __init__ 方法中,我们调用 super().__init__() 初始化父类,然后调用 initUI() 方法来设置界面。
  • initUI 中,我们创建了布局、标签和按钮,并将它们添加到布局中。我们将 labelbutton 保存为 self.labelself.button,以便在类的其他方法中访问它们。
  • 关键一步:self.button.clicked.connect(self.change_label_text)。我们将按钮的 clicked 信号连接到 MyWindow 类的 change_label_text 方法。
  • change_label_text 方法是我们的槽。当按钮被点击时,这个方法就会被调用,并将 self.label 的文本修改为“文本已改变!”。
  • 在主程序入口 if __name__ == '__main__': 中,我们创建 QApplication 实例,然后创建 MyWindow 类的实例,显示窗口,并启动事件循环。

运行这段代码,点击按钮,你会发现标签的文本从“初始文本”变成了“文本已改变!”,同时控制台会打印“按钮被点击了!”。这成功地实现了通过信号与槽进行控件交互。

信号与槽示例:获取输入框文本并更新标签

让我们再看一个例子,结合 QLineEditQPushButton,点击按钮后将输入框的文本显示在标签上。

“`python
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QPushButton, QVBoxLayout

class InputGreetWindow(QWidget):
def init(self):
super().init()
self.initUI()

def initUI(self):
    self.setWindowTitle('获取输入文本示例')
    self.setGeometry(100, 100, 300, 150)

    layout = QVBoxLayout()
    self.setLayout(layout)

    # 创建输入框和标签
    self.name_input = QLineEdit()
    self.name_input.setPlaceholderText("请输入你的名字")

    self.greet_button = QPushButton('点击问候')

    self.result_label = QLabel('等待输入...')

    # 添加控件到布局
    layout.addWidget(self.name_input)
    layout.addWidget(self.greet_button)
    layout.addWidget(self.result_label)

    # 连接信号与槽
    self.greet_button.clicked.connect(self.greet_user)

# 槽方法:获取输入框文本并更新标签
def greet_user(self):
    # 获取 QLineEdit 的当前文本
    name = self.name_input.text()

    if name: # 如果输入不为空
        greeting = f"你好,{name}!"
    else:
        greeting = "请输入你的名字!"

    # 更新 QLabel 的文本
    self.result_label.setText(greeting)

应用程序入口

if name == ‘main‘:
app = QApplication(sys.argv)
main_window = InputGreetWindow()
main_window.show()
sys.exit(app.exec_())
“`

代码解释:

  • 我们创建了一个 InputGreetWindow 类。
  • 创建了 QLineEdit (保存为 self.name_input)、QPushButton (保存为 self.greet_button) 和 QLabel (保存为 self.result_label)。
  • 将按钮的 clicked 信号连接到 self.greet_user 方法。
  • greet_user 槽方法中:
    • name = self.name_input.text(): 调用 QLineEdittext() 方法来获取用户输入的当前文本。
    • 根据获取的文本构建问候语。
    • self.result_label.setText(greeting): 调用 QLabelsetText() 方法来更新标签显示的文本。

运行此代码,在文本框中输入你的名字,然后点击按钮,下方的标签就会显示相应的问候语。这充分展示了信号与槽在实现用户交互中的核心作用。

信号与槽机制是 PyQt/Qt 开发中最重要的概念之一,理解并熟练运用它对于构建响应式 GUI 至关重要。

第七章:使用 Qt Designer

手动编写代码来创建控件和布局,特别是对于复杂的界面,会变得非常繁琐和耗时。Qt 提供了一个强大的可视化界面设计工具——Qt Designer。使用 Qt Designer,你可以通过拖拽的方式创建界面,设置控件的属性和布局,然后将设计保存为 .ui 文件。PyQt 提供了工具将 .ui 文件转换为 Python 代码,你就可以在你的应用程序中加载和使用这些界面。

启动 Qt Designer

如果你按照第二章的要求安装了 pyqt5-toolspyqt6-tools,你可以在你的 Python 环境目录下找到 designer.exe (Windows) 或 designer (macOS/Linux) 并运行它。

首次打开 Qt Designer,它会让你选择一个模板来创建新的窗体(Form)。常见的模板有:

  • Main Window (主窗口): 带有菜单栏、工具栏、状态栏的标准应用窗口,通常继承自 QMainWindow
  • Widget (控件): 一个通用的空白窗体,可以作为顶级窗口或嵌入到其他窗体中,通常继承自 QWidget
  • Dialog (对话框): 用于弹出式交互,如文件选择、消息提示等,通常继承自 QDialog

对于入门学习,选择 WidgetMain Window 都可以。选择一个模板,然后点击“Create”(创建)。

Qt Designer 界面简介

Qt Designer 的界面主要包含以下几个区域:

  1. Widget Box (控件箱): 位于左侧,列出了所有可用的标准 Qt 控件,你可以从中拖拽控件到窗体上。
  2. Form Editor (窗体编辑器): 位于中央区域,是你进行可视化设计的地方,拖拽控件到这里,调整它们的位置和大小。
  3. Object Inspector (对象查看器): 位于右上方,以树状结构显示窗体上的所有控件对象及其层级关系。每个控件都有一个对象名称(objectName),这个名称在后续的 Python 代码中会用到。
  4. Property Editor (属性编辑器): 位于右下方,显示当前选中控件的所有属性及其值,你可以在这里修改控件的文本、大小、颜色、字体等各种属性。
  5. Signals & Slots Editor (信号与槽编辑器): 在底部区域(可能需要切换到对应的模式),允许你在 Designer 中直接建立信号与槽的连接(对于简单的连接)。
  6. Layouts Toolbar (布局工具栏): 在窗体编辑器上方,提供了应用各种布局管理器的按钮。

使用 Qt Designer 设计一个简单界面

让我们设计一个包含标签、输入框和按钮的界面:

  1. 打开 Qt Designer,选择创建一个 Widget
  2. Widget Box 中拖拽一个 Label 到窗体上。双击 Label 可以编辑其文本,例如输入“请输入您的消息:”。
  3. Widget Box 中拖拽一个 Line Edit 到窗体上。
  4. Widget Box 中拖拽一个 Push Button 到窗体上,双击按钮编辑文本,例如输入“显示消息”。
  5. Widget Box 中再拖拽一个 Label 到窗体上,用于显示结果,可以清空其文本或设置为占位符文本。
  6. 在窗体上选中所有控件(按住 Shift 或 Ctrl 点击),然后在上方的布局工具栏中选择一个布局,例如 Vertical Layout (垂直布局) 或 Grid Layout (网格布局)。这里我们选择垂直布局,控件会自动按照垂直方向排列。
  7. 选中窗体本身(点击窗体的背景区域),在 Property Editor 中找到 windowTitle 属性,输入窗口标题,例如“消息显示器”。
  8. 选中每个控件,观察 Object Inspector 中的 objectName,这是这个控件在生成的 Python 代码中的变量名。你可以修改它们,使其更具描述性,例如将 Line Edit 改为 messageLineEdit,将 Push Button 改为 displayButton,将结果 Label 改为 resultLabel
  9. 保存文件,选择一个位置,将文件类型设置为 Qt Designer Form (*.ui),文件名例如 message_app.ui

将 .ui 文件转换为 .py 代码

保存 .ui 文件后,我们需要将其转换为 Python 代码。PyQt 提供了 pyuic5 (或 pyuic6 for PyQt6) 这个命令行工具来完成转换。

打开终端或命令提示符,切换到你的 .ui 文件所在的目录,然后执行以下命令:

bash
pyuic5 message_app.ui -o ui_message_app.py

  • pyuic5: 转换工具的名称。
  • message_app.ui: 输入的 .ui 文件名。
  • -o ui_message_app.py: 指定输出的 Python 文件名(通常以 ui_ 开头)。

执行成功后,会生成一个名为 ui_message_app.py 的 Python 文件。打开这个文件,你会看到它定义了一个类(通常是 Ui_FormUi_MainWindow,取决于你选择的模板),其中包含了创建和设置你在 Designer 中添加的所有控件和布局的代码。

在 Python 应用程序中加载和使用生成的界面

生成的 ui_*.py 文件通常不直接运行,它是用来描述界面的。你需要在另一个 Python 文件中导入这个界面类,并在你的主窗口类中使用它。

创建一个新的 Python 文件(例如 main_app.py),编写以下代码:

“`python
import sys
from PyQt5.QtWidgets import QApplication, QWidget

从生成的 UI 文件中导入界面类

from ui_message_app import Ui_Form

class MessageWindow(QWidget, Ui_Form): # 继承自 QWidget 和 生成的界面类
def init(self):
super().init() # 调用 QWidget 的构造函数
self.setupUi(self) # 调用生成的界面类中的 setupUi 方法来初始化界面

    # 现在你可以通过 self.objectName 来访问在 Designer 中创建的控件
    # 例如:self.messageLineEdit, self.displayButton, self.resultLabel

    # 连接信号与槽 (在这里定义你的逻辑)
    self.displayButton.clicked.connect(self.display_message)

# 槽方法
def display_message(self):
    message = self.messageLineEdit.text() # 获取输入框文本
    self.resultLabel.setText(f"您输入的消息是: {message}") # 更新结果标签

应用程序入口

if name == ‘main‘:
app = QApplication(sys.argv)
main_window = MessageWindow() # 创建自定义窗口实例
main_window.show() # 显示窗口
sys.exit(app.exec_()) # 进入事件循环
“`

代码解释:

  • from ui_message_app import Ui_Form: 导入由 pyuic5 生成的界面类。
  • class MessageWindow(QWidget, Ui_Form):: 我们的主窗口类同时继承自 QWidget (或 QMainWindow,取决于你的 UI 模板) 和生成的界面类 Ui_Form。这种多重继承的方式是使用 Designer 生成界面的常见模式。
  • self.setupUi(self): 在 __init__ 方法中调用 setupUi 是关键。这个方法由 Ui_Form 类提供,它负责在当前的 MessageWindow 实例(也就是 self)上创建你在 Designer 中设计的控件和布局。
  • 调用 self.setupUi(self) 后,你就可以通过你在 Designer 中为控件设置的 objectName 来访问它们,例如 self.messageLineEditself.displayButtonself.resultLabel
  • 然后,像之前一样,连接信号与槽来添加应用程序的交互逻辑。

运行 main_app.py 文件,你就会看到你在 Qt Designer 中设计的界面。在输入框中输入文本,点击按钮,下方的标签会显示输入的消息。

使用 Qt Designer 可以极大地提高界面设计的效率,特别是对于复杂的布局。它让界面设计和业务逻辑的编写分离,使得代码结构更清晰。

第八章:一个更完整的例子

让我们结合前面学到的知识,创建一个稍微复杂一点的例子:一个简单的温度转换器。界面包含两个输入框(分别用于输入摄氏度和华氏度)、两个标签、以及两个按钮(分别用于进行转换)。

我们将使用布局管理器来组织界面,并使用信号与槽来实现转换逻辑。

“`python
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QLineEdit,
QPushButton, QGridLayout, QMessageBox)

class TemperatureConverter(QWidget):
def init(self):
super().init()
self.initUI()

def initUI(self):
    self.setWindowTitle('温度转换器')
    self.setGeometry(100, 100, 300, 150)

    # 创建网格布局
    grid = QGridLayout()
    self.setLayout(grid)

    # 创建控件
    self.celsius_label = QLabel('摄氏度 (°C):')
    self.celsius_input = QLineEdit()
    self.celsius_input.setPlaceholderText("输入摄氏度")

    self.fahrenheit_label = QLabel('华氏度 (°F):')
    self.fahrenheit_input = QLineEdit()
    self.fahrenheit_input.setPlaceholderText("输入华氏度")

    self.to_fahrenheit_button = QPushButton('转为华氏度')
    self.to_celsius_button = QPushButton('转为摄氏度')

    # 将控件添加到网格布局
    grid.addWidget(self.celsius_label, 0, 0) # 行0, 列0
    grid.addWidget(self.celsius_input, 0, 1) # 行0, 列1
    grid.addWidget(self.to_fahrenheit_button, 0, 2) # 行0, 列2

    grid.addWidget(self.fahrenheit_label, 1, 0) # 行1, 列0
    grid.addWidget(self.fahrenheit_input, 1, 1) # 行1, 列1
    grid.addWidget(self.to_celsius_button, 1, 2) # 行1, 列2

    # (可选) 设置列伸缩因子,使输入框列填充空间
    grid.setColumnStretch(1, 1)

    # 连接信号与槽
    self.to_fahrenheit_button.clicked.connect(self.convert_celsius_to_fahrenheit)
    self.to_celsius_button.clicked.connect(self.convert_fahrenheit_to_celsius)

    # 也可以连接输入框的 textChanged 信号实现实时转换 (此处使用按钮触发)
    # self.celsius_input.textChanged.connect(self.convert_celsius_to_fahrenheit)
    # self.fahrenheit_input.textChanged.connect(self.convert_fahrenheit_to_celsius)


# 槽方法:摄氏度转华氏度
def convert_celsius_to_fahrenheit(self):
    try:
        # 获取摄氏度输入
        celsius_text = self.celsius_input.text()
        if not celsius_text: # 如果输入为空,清空华氏度输入框
            self.fahrenheit_input.clear()
            return

        celsius = float(celsius_text)
        # 执行转换
        fahrenheit = (celsius * 9/5) + 32
        # 在华氏度输入框显示结果 (保留两位小数)
        self.fahrenheit_input.setText(f"{fahrenheit:.2f}")
    except ValueError:
        # 处理输入不是有效数字的情况
        QMessageBox.warning(self, "输入错误", "请输入有效的数字!")
        self.celsius_input.clear() # 清空错误输入
        self.fahrenheit_input.clear() # 清空结果

# 槽方法:华氏度转摄氏度
def convert_fahrenheit_to_celsius(self):
    try:
        # 获取华氏度输入
        fahrenheit_text = self.fahrenheit_input.text()
        if not fahrenheit_text: # 如果输入为空,清空摄氏度输入框
            self.celsius_input.clear()
            return

        fahrenheit = float(fahrenheit_text)
        # 执行转换
        celsius = (fahrenheit - 32) * 5/9
        # 在摄氏度输入框显示结果 (保留两位小数)
        self.celsius_input.setText(f"{celsius:.2f}")
    except ValueError:
        # 处理输入不是有效数字的情况
        QMessageBox.warning(self, "输入错误", "请输入有效的数字!")
        self.fahrenheit_input.clear() # 清空错误输入
        self.celsius_input.clear() # 清空结果

应用程序入口

if name == ‘main‘:
app = QApplication(sys.argv)
converter_window = TemperatureConverter()
converter_window.show()
sys.exit(app.exec_())
“`

代码解释:

  • 我们创建了一个 TemperatureConverter 类继承自 QWidget
  • initUI 中,我们使用 QGridLayout 来组织控件。标签和输入框分别放在两行,按钮放在每行的第三列。
  • 创建 QLabelQLineEditQPushButton 实例,并使用 grid.addWidget() 将它们添加到布局的指定位置。
  • 我们连接了两个按钮的 clicked 信号到对应的转换槽方法 (convert_celsius_to_fahrenheitconvert_fahrenheit_to_celsius)。
  • 在转换槽方法中:
    • 使用 self.celsius_input.text()self.fahrenheit_input.text() 获取输入框的文本。
    • 使用 float() 尝试将文本转换为浮点数。
    • 使用 try...except ValueError 来捕获用户输入非数字时可能发生的错误。如果发生错误,使用 QMessageBox.warning() 弹出一个警告框提示用户。
    • 执行温度转换计算。
    • 使用 setText() 方法将计算结果更新到对应的输入框中。使用 f-string 和 : .2f 格式化字符串来保留两位小数。
    • 添加了输入为空时的处理逻辑,清空另一个输入框。

这个例子虽然简单,但它结合了布局管理、常用控件的使用、信号与槽的连接以及基本的错误处理,是一个更完整的 GUI 应用程序示例。你可以根据需要,在这个基础上添加更多功能或改进界面。

第九章:进阶与资源

恭喜你!通过前面的学习,你已经掌握了使用 PyQt 进行 GUI 设计的基础知识,包括:

  • PyQt 的核心概念 (QApplication, QWidget, 事件循环)
  • 常用控件 (QLabel, QPushButton, QLineEdit, QTextEdit, QCheckBox, QRadioButton)
  • 布局管理 (QVBoxLayout, QHBoxLayout, QGridLayout)
  • 信号与槽机制
  • 使用 Qt Designer 加速界面开发

这为你打开了使用 Python 构建桌面应用程序的大门。但是,PyQt/Qt 的功能远不止于此。以下是一些可以进一步学习的方向:

  • 更高级的控件: QListView, QTreeView, QTableView (列表、树、表格视图), QComboBox (下拉框), QSlider (滑块), QSpinBox (数字选择框), QProgressBar (进度条) 等等。
  • 对话框 (Dialogs): QMessageBox (消息框), QFileDialog (文件选择框), QFontDialog (字体选择框), QColorDialog (颜色选择框) 以及自定义对话框 (QDialog)。
  • 主窗口 (QMainWindow): 学习如何使用 QMainWindow 创建带有菜单栏 (QMenuBar)、工具栏 (QToolBar)、状态栏 (QStatusBar) 的标准桌面应用程序。
  • 事件处理: 更深入地了解 Qt 的事件系统,如何重写控件的事件处理方法(如 mousePressEvent, keyPressEvent, closeEvent)来响应特定事件。
  • 自定义控件: 学习如何继承现有的控件或 QWidget 来创建具有特定外观和行为的自定义控件。
  • 图形和绘图: 利用 QPainter 类在控件上进行自定义绘图。
  • 模型/视图架构 (Model/View Architecture): 学习如何使用模型/视图框架处理大量数据(如在 QTableView 中显示数据库内容),实现数据与界面的分离。
  • 多线程: 在 GUI 应用中使用多线程处理耗时任务,避免阻塞主线程导致界面冻结。
  • 网络编程、数据库访问等 Qt 其他模块: 学习如何利用 Qt 提供的其他模块进行更广泛的开发。
  • 样式表 (Qt Style Sheets): 使用 CSS 类似的语法来美化你的界面。
  • 国际化 (Internationalization): 使你的应用程序支持多种语言。

学习资源

  • 官方 PyQt 文档: 尽管有时比较技术性,但它是最权威的参考资料。搜索 “PyQt5 Documentation” 或 “PyQt6 Documentation”。
  • 官方 Qt 文档: 由于 PyQt 是 Qt 的绑定,Qt 的 C++ 文档对于理解底层概念和类的功能非常有帮助。搜索 “Qt Documentation”。
  • PyQt 教程网站: 有很多网站和博客提供了 PyQt 的入门到进阶教程,例如 ZetCode, Real Python 等(可以搜索 “PyQt5 tutorial”)。
  • GitHub 和其他代码仓库: 查看其他人使用 PyQt 开发的开源项目,学习他们的代码结构和实现方式。
  • 社区论坛: 在 Stack Overflow、Reddit 等社区搜索或提问与 PyQt 相关的问题。

持续实践和动手尝试是掌握 PyQt 的最好方法。尝试用 PyQt 实现一些小工具或你自己的想法,这会帮助你更好地理解和运用所学的知识。

结论

PyQt 是一个强大、成熟且灵活的 Python GUI 开发框架。通过本文的详细介绍,你应该对 PyQt 的基本原理、常用组件和开发流程有了清晰的认识。从第一个窗口的创建,到常用控件的使用、布局管理、信号与槽的交互机制,再到可视化设计工具 Qt Designer 的应用,我们一步步构建了 PyQt 应用的基础。

GUI 设计是一个既包含技术实现也包含用户体验考虑的领域。PyQt 提供了强大的技术支持,使得开发者可以专注于构建出色的用户界面。希望本文能为你探索 PyQt 世界提供一个良好的起点,鼓励你继续学习和实践,开发出更多富有创意和实用价值的 GUI 应用程序!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部