PyQt5 是 Python 里基于 Qt 框架的 GUI 开发工具,能做桌面应用,跨平台(Windows/macOS/Linux 都能用)。你可能想知道:怎么开始用?有哪些核心组件?怎么写界面逻辑?别急,咱们一步步拆解,多写代码示例,你跟着敲一遍就清楚了。
一、环境搭建:先让 PyQt5 跑起来
首先得安装 PyQt5。打开终端(命令提示符),输入:
pip install pyqt5
如果想设计界面更方便,可以装个 Qt Designer(官方推荐的可视化工具):
pip install pyqt5-tools
安装后,在 Python 安装目录的 Scripts
文件夹里,能找到 designer.exe
(Windows 系统),双击就能用。
二、第一个 PyQt5 程序:Hello World 窗口
先写个最基础的窗口程序,看看 PyQt5 的基本结构。代码如下:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
if __name__ == "__main__":
# 创建应用程序对象
app = QApplication(sys.argv)
# 创建主窗口
window = QMainWindow()
window.setWindowTitle("PyQt5 Hello World") # 设置窗口标题
window.setGeometry(100, 100, 300, 200) # 设置窗口位置和大小(x, y, 宽, 高)
# 创建标签组件
label = QLabel("Hello, PyQt5!", window)
label.setGeometry(50, 50, 200, 30) # 设置标签位置和大小
# 显示窗口
window.show()
# 进入应用程序主循环
sys.exit(app.exec_())
代码解析:
QApplication
:管理应用程序的资源,每个程序必须有且仅有一个实例。QMainWindow
:主窗口类,包含标题栏、菜单栏、工具栏等标准组件。QLabel
:标签组件,用于显示文本或图片。setGeometry()
:前两个参数是组件的坐标(相对于父容器),后两个是宽高。app.exec_()
:启动事件循环,让窗口保持显示状态,直到用户关闭窗口。
运行后会看到一个带 "Hello, PyQt5!" 文本的窗口,这就是最基础的 PyQt5 程序。
三、组件布局:让界面更整齐
刚才的例子用 setGeometry()
手动设置组件位置,适合简单界面,但复杂界面需要布局管理器(Layout)自动排列组件。PyQt5 有四种常用布局:
- 水平布局(QHBoxLayout):组件水平排列
- 垂直布局(QVBoxLayout):组件垂直排列
- 网格布局(QGridLayout):组件按行、列网格排列
- 表单布局(QFormLayout):标签和输入框左右排列(类似表单)
示例:水平布局 + 按钮点击事件
import sys
from PyQt5.QtWidgets import (
QApplication, QWidget, QHBoxLayout, QPushButton, QLabel
)
class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# 创建水平布局
layout = QHBoxLayout()
# 创建按钮和标签
self.btn = QPushButton("点击我", self)
self.label = QLabel("未点击", self)
# 按钮点击时连接到槽函数
self.btn.clicked.connect(self.on_click)
# 将组件添加到布局中
layout.addWidget(self.btn)
layout.addWidget(self.label)
# 设置窗口的布局
self.setLayout(layout)
# 设置窗口属性
self.setWindowTitle("布局示例")
self.setGeometry(100, 100, 300, 100)
def on_click(self):
# 按钮点击后的逻辑:修改标签文本
self.label.setText("已点击!")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
关键点:
QWidget
:基础容器组件,可作为独立窗口或其他组件的父容器。layout.addWidget()
:将组件添加到布局中,会自动排列。clicked.connect()
:连接信号(clicked
)和槽函数(on_click
),点击按钮时触发函数。
四、常用组件详解
PyQt5 有几十种组件,这里挑最常用的讲,每个组件配一个代码示例。
1. 按钮(QPushButton)
除了点击事件,还可以设置图标、快捷键等。
from PyQt5.QtGui import QIcon, QKeySequence
from PyQt5.QtCore import Qt
# 创建带图标的按钮
btn = QPushButton(QIcon("icon.png"), "按钮", self)
btn.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_S)) # 设置快捷键 Ctrl+S
2. 文本输入框(QLineEdit)
单行输入框,支持输入验证、掩码等。
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QLabel, QVBoxLayout
class LineEditDemo(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
layout = QVBoxLayout()
# 创建输入框
self.edit = QLineEdit()
self.edit.setPlaceholderText("请输入内容") # 提示文本
self.edit.textChanged.connect(self.on_text_change) # 文本变化时触发
# 创建标签显示输入内容
self.label = QLabel("内容:")
layout.addWidget(self.edit)
layout.addWidget(self.label)
self.setLayout(layout)
self.setWindowTitle("文本输入框示例")
self.setGeometry(100, 100, 300, 150)
def on_text_change(self, text):
self.label.setText(f"内容:{text}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = LineEditDemo()
window.show()
sys.exit(app.exec_())
3. 文本编辑框(QTextEdit)
多行文本输入,支持富文本(HTML)。
from PyQt5.QtWidgets import QTextEdit
text_edit = QTextEdit()
text_edit.setHtml("<h1>加粗文本</h1><p>普通文本</p>") # 设置富文本
print(text_edit.toPlainText()) # 获取纯文本内容
4. 下拉框(QComboBox)
combo = QComboBox()
combo.addItems(["选项1", "选项2", "选项3"])
combo.currentIndexChanged.connect(lambda idx: print(f"选中索引:{idx}"))
5. 复选框(QCheckBox)
check_box = QCheckBox("记住密码")
check_box.toggled.connect(lambda is_checked: print(f"是否勾选:{is_checked}"))
6. 单选按钮(QRadioButton)
layout = QHBoxLayout()
radio1 = QRadioButton("男")
radio2 = QRadioButton("女")
radio1.setChecked(True) # 默认选中
layout.addWidget(radio1)
layout.addWidget(radio2)
7. 列表视图(QListView)
from PyQt5.QtCore import QStringListModel
model = QStringListModel()
model.setStringList(["苹果", "香蕉", "橙子"])
list_view = QListView()
list_view.setModel(model)
五、对话框(Dialog):与用户交互的关键
PyQt5 提供了多种预定义对话框,比如消息框、文件选择框、颜色选择框等。
1. 消息框(QMessageBox)
from PyQt5.QtWidgets import QMessageBox
# 信息提示框
QMessageBox.information(self, "提示", "操作成功!")
# 警告框
QMessageBox.warning(self, "警告", "文件未保存!")
# 确认框(返回用户点击的按钮)
reply = QMessageBox.question(
self, "确认", "是否退出程序?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
)
if reply == QMessageBox.Yes:
sys.exit()
2. 文件选择框(QFileDialog)
from PyQt5.QtWidgets import QFileDialog
# 选择单个文件(返回文件路径)
file_path, _ = QFileDialog.getOpenFileName(
self, "选择文件", "", "文本文件 (*.txt);;所有文件 (*.*)"
)
if file_path:
with open(file_path, "r") as f:
content = f.read()
# 选择文件夹(返回文件夹路径)
folder_path = QFileDialog.getExistingDirectory(self, "选择文件夹")
3. 颜色选择框(QColorDialog)
from PyQt5.QtGui import QColor
color = QColorDialog.getColor()
if color.isValid():
print(f"选择的颜色:RGB({color.red()}, {color.green()}, {color.blue()})")
六、主窗口结构:QMainWindow 的高级用法
QMainWindow
是应用程序的主窗口,包含以下重要区域:
- 菜单栏(Menu Bar):位于顶部,包含多个菜单(如文件、编辑)。
- 工具栏(Tool Bar):位于菜单栏下方,包含常用功能按钮。
- 中心部件(Central Widget):主窗口中间的区域,用于放置主要内容。
- 状态栏(Status Bar):位于底部,显示状态信息。
示例:完整主窗口结构
import sys
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QAction, QMenu, QTextEdit, QMessageBox
)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# 设置中心部件(文本编辑框)
self.text_edit = QTextEdit()
self.setCentralWidget(self.text_edit)
# 创建菜单栏
menu_bar = self.menuBar()
# 文件菜单
file_menu = menu_bar.addMenu("文件(&F)") # &F 表示快捷键 Alt+F
# 新建动作
new_action = QAction("新建(&N)", self)
new_action.setShortcut("Ctrl+N")
new_action.triggered.connect(self.new_file)
# 打开动作
open_action = QAction("打开(&O)", self)
open_action.setShortcut("Ctrl+O")
open_action.triggered.connect(self.open_file)
# 退出动作
exit_action = QAction("退出(&X)", self)
exit_action.setShortcut("Ctrl+Q")
exit_action.triggered.connect(self.close)
# 将动作添加到文件菜单
file_menu.addAction(new_action)
file_menu.addAction(open_action)
file_menu.addSeparator() # 添加分隔线
file_menu.addAction(exit_action)
# 编辑菜单(带子菜单)
edit_menu = menu_bar.addMenu("编辑(&E)")
format_submenu = edit_menu.addMenu("格式")
bold_action = QAction("加粗", self)
italic_action = QAction("斜体", self)
format_submenu.addAction(bold_action)
format_submenu.addAction(italic_action)
# 创建工具栏
tool_bar = self.addToolBar("常用工具")
tool_bar.addAction(new_action)
tool_bar.addAction(open_action)
# 创建状态栏
self.status_bar = self.statusBar()
self.status_bar.showMessage("就绪", 3000) # 显示消息3秒
# 窗口设置
self.setWindowTitle("主窗口示例")
self.setGeometry(100, 100, 800, 600)
def new_file(self):
self.text_edit.clear()
self.status_bar.showMessage("新建文件", 2000)
def open_file(self):
file_path, _ = QFileDialog.getOpenFileName(self, "打开文件", "", "文本文件 (*.txt)")
if file_path:
try:
with open(file_path, "r") as f:
self.text_edit.setText(f.read())
self.status_bar.showMessage(f"打开文件:{file_path}", 2000)
except Exception as e:
QMessageBox.warning(self, "错误", f"打开文件失败:{str(e)}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
关键组件说明:
QAction
:菜单栏、工具栏中的动作项,可关联快捷键和函数。setCentralWidget()
:设置主窗口的中心部件(只能有一个)。statusBar()
:获取状态栏,用于显示临时消息。
七、信号与槽:组件交互的核心机制
PyQt5 的核心是 信号与槽(Signals and Slots):
- 信号(Signal):组件状态变化时发出的通知(如按钮点击、输入框文本变化)。
- 槽(Slot):接收信号并执行的函数(可以是普通函数或 lambda 表达式)。
信号与槽的三种连接方式
- 直接连接(最常用):
btn.clicked.connect(self.on_click) # 连接到类中的方法
- 连接到 lambda 表达式:
btn.clicked.connect(lambda: print("按钮被点击"))
- 断开连接(可选):
btn.clicked.disconnect(self.on_click) # 断开信号与槽的连接
自定义信号:跨组件通信
如果需要在自定义的类之间传递数据,可以定义自己的信号。
from PyQt5.QtCore import pyqtSignal, QObject
class MySignal(QObject):
# 定义一个带参数的信号(字符串类型)
my_signal = pyqtSignal(str)
# 使用信号
signal_obj = MySignal()
signal_obj.my_signal.connect(lambda msg: print(f"收到信号:{msg}"))
signal_obj.my_signal.emit("你好,自定义信号!") # 发送信号,输出:收到信号:你好,自定义信号!
八、Qt Designer 的使用:可视化设计界面
手动写代码布局复杂界面效率低,推荐用 Qt Designer 可视化设计,再将设计文件转换为 Python 代码。
步骤 1:用 Qt Designer 设计界面
- 打开
designer.exe
,选择模板(如 Main Window)。 - 从左侧组件栏拖放组件到窗口中,调整布局(右键点击组件选择布局)。
- 保存为
.ui
文件(例如main.ui
)。
步骤 2:将 .ui
文件转换为 Python 代码
用命令行工具 pyuic5
转换:
pyuic5 -o main_window.py main.ui
转换后的代码会生成一个继承自 QMainWindow
的类,包含所有组件的定义。
步骤 3:在 Python 中加载界面并添加逻辑
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from main_window import Ui_MainWindow # 导入转换后的 UI 类
class MyApp(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self) # 初始化 UI
self.btn_ok.clicked.connect(self.on_ok_click) # 连接按钮点击事件
def on_ok_click(self):
text = self.line_edit.text()
self.label_result.setText(f"你输入了:{text}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())
优点: 界面设计和逻辑代码分离,修改界面无需改代码,适合团队协作。
九、多线程处理:避免界面卡顿
PyQt5 中,主线程(UI 线程)负责渲染界面和处理用户交互,而耗时操作(如网络请求、文件读写、大数据计算等)若直接在主线程执行,会导致界面卡顿甚至无响应。此时需使用多线程将耗时任务放到子线程中执行,确保 UI 流畅。
PyQt5 多线程核心要点
- 避免在子线程中直接操作 UI 组件:
子线程不能直接修改 UI 组件(可能引发线程安全问题),需通过信号与槽机制将结果传递给主线程,由主线程更新界面。 - 使用
QThread
或QThreadPool
:QThread
:创建独立线程,适合单个长时间任务。QThreadPool
:线程池,适合管理多个短时间任务,避免频繁创建 / 销毁线程的开销。
示例 1:使用 QThread
实现耗时任务
以下代码演示一个模拟耗时任务(循环计数),通过信号传递进度和结果到主线程更新界面。
import sys
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import (
QApplication, QWidget, QPushButton, QLabel, QVBoxLayout, QProgressBar
)
# ----------------------
# 自定义子线程类(继承 QThread)
# ----------------------
class WorkerThread(QThread):
# 定义信号:用于传递进度(int)和完成通知(无参数)
progress_signal = pyqtSignal(int) # 进度信号
finish_signal = pyqtSignal() # 完成信号
def __init__(self, total_steps=10):
super().__init__()
self.total_steps = total_steps # 总任务步数
def run(self):
"""子线程的核心逻辑(耗时任务)"""
for step in range(self.total_steps + 1):
# 模拟耗时操作(这里用 sleep 代替)
self.sleep(1) # 暂停 1 秒(需导入 from PyQt5.QtCore import QThread)
# 发送进度信号(step 从 0 到 total_steps)
self.progress_signal.emit(step)
# 任务完成后发送完成信号
self.finish_signal.emit()
# ----------------------
# 主窗口类
# ----------------------
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.init_thread() # 初始化子线程
def initUI(self):
"""设置界面布局"""
layout = QVBoxLayout()
# 启动按钮
self.start_btn = QPushButton("开始任务", self)
self.start_btn.clicked.connect(self.start_task) # 点击时启动任务
# 进度条
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 100) # 进度范围 0~100
# 结果标签
self.result_label = QLabel("等待任务开始...")
layout.addWidget(self.start_btn)
layout.addWidget(self.progress_bar)
layout.addWidget(self.result_label)
self.setLayout(layout)
self.setWindowTitle("多线程示例")
self.setGeometry(100, 100, 300, 150)
def init_thread(self):
"""初始化子线程并连接信号"""
self.worker = WorkerThread(total_steps=10) # 创建子线程实例
# 连接子线程的信号到主线程的槽函数
self.worker.progress_signal.connect(self.update_progress) # 进度更新
self.worker.finish_signal.connect(self.task_finished) # 任务完成
# 线程结束后自动销毁(避免内存泄漏)
self.worker.finished.connect(self.worker.deleteLater)
def start_task(self):
"""启动子线程任务"""
# 禁用按钮防止重复启动
self.start_btn.setEnabled(False)
self.result_label.setText("任务进行中...")
# 启动子线程(会自动调用 worker.run())
self.worker.start()
def update_progress(self, step):
"""更新进度条(由 progress_signal 触发)"""
progress = int(step / 10 * 100) # 将 step(0~10)转为百分比(0~100)
self.progress_bar.setValue(progress)
def task_finished(self):
"""任务完成后的处理(由 finish_signal 触发)"""
self.result_label.setText("任务完成!")
self.start_btn.setEnabled(True) # 重新启用按钮
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
代码解析:
-
子线程类
WorkerThread
:- 继承自
QThread
,重写run()
方法作为线程入口。 progress_signal
和finish_signal
用于向主线程传递进度和完成状态。self.sleep(1)
模拟耗时操作(实际开发中替换为真实任务,如文件读写)。
- 继承自
-
主线程与子线程通信:
- 通过
connect()
将子线程的信号连接到主线程的槽函数(update_progress
和task_finished
)。 - 子线程通过
emit()
发送信号,主线程接收到信号后安全地更新 UI。
- 通过
-
线程管理:
worker.finished.connect(worker.deleteLater)
:线程结束后自动释放资源,避免内存泄漏。- 按钮在任务启动时禁用,完成后重新启用,防止重复启动。
示例 2:使用 QThreadPool
和 QRunnable
管理多任务
QThreadPool
适用于管理多个短时间任务(如同时加载多个图片、处理多个数据文件),通过 QRunnable
封装任务逻辑。
import sys
from PyQt5.QtCore import QRunnable, pyqtSignal, pyqtSlot, QThreadPool
from PyQt5.QtWidgets import (
QApplication, QWidget, QPushButton, QLabel, QVBoxLayout, QListWidget
)
# ----------------------
# 自定义任务类(继承 QRunnable)
# ----------------------
class Task(QRunnable):
def __init__(self, task_id):
super().__init__()
self.task_id = task_id # 任务编号
self.result = None # 任务结果
@pyqtSlot() # 必须使用 pyqtSlot 装饰器作为入口
def run(self):
"""任务逻辑:模拟耗时计算(这里计算任务编号的平方)"""
import time
time.sleep(2) # 模拟耗时 2 秒
self.result = f"任务 {self.task_id} 结果:{self.task_id ** 2}"
# 发送信号(通过自定义信号或直接调用主线程方法)
MainWindow.task_completed_signal.emit(self.result) # 直接调用主线程的信号
# ----------------------
# 主窗口类
# ----------------------
class MainWindow(QWidget):
# 定义类级别的信号(用于任务完成通知)
task_completed_signal = pyqtSignal(str)
def __init__(self):
super().__init__()
self.initUI()
self.init_thread_pool()
def initUI(self):
layout = QVBoxLayout()
# 启动多任务按钮
self.start_tasks_btn = QPushButton("启动 3 个任务", self)
self.start_tasks_btn.clicked.connect(self.start_tasks)
# 任务结果列表
self.result_list = QListWidget()
layout.addWidget(self.start_tasks_btn)
layout.addWidget(self.result_list)
self.setLayout(layout)
self.setWindowTitle("线程池示例")
self.setGeometry(100, 100, 300, 200)
# 连接任务完成信号到槽函数
self.task_completed_signal.connect(self.add_result_to_list)
def init_thread_pool(self):
"""初始化线程池(全局单例,无需手动创建实例)"""
self.thread_pool = QThreadPool.globalInstance()
print(f"线程池最大线程数:{self.thread_pool.maxThreadCount()}") # 默认根据 CPU 核心数自动设置
def start_tasks(self):
"""启动多个任务并添加到线程池"""
self.start_tasks_btn.setEnabled(False)
for task_id in range(1, 4):
task = Task(task_id=task_id)
self.thread_pool.start(task) # 将任务添加到线程池执行
def add_result_to_list(self, result):
"""将任务结果添加到列表(由信号触发)"""
self.result_list.addItem(result)
if self.result_list.count() == 3: # 所有任务完成后恢复按钮
self.start_tasks_btn.setEnabled(True)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
代码解析:
-
任务类
Task
:- 继承自
QRunnable
,重写run()
方法(需用@pyqtSlot()
装饰)。 - 任务逻辑中通过
time.sleep(2)
模拟耗时操作,计算任务编号的平方作为结果。 - 通过主线程的
task_completed_signal
发送结果,避免子线程直接操作 UI。
- 继承自
-
线程池
QThreadPool
:- 使用全局实例
QThreadPool.globalInstance()
,无需手动管理线程生命周期。 thread_pool.start(task)
将任务添加到线程池,线程池自动分配空闲线程执行任务。
- 使用全局实例
-
批量任务管理:
- 启动 3 个任务,每个任务完成后通过信号更新结果列表。
- 所有任务完成后恢复按钮状态,避免重复启动。
多线程注意事项
-
避免跨线程操作 UI:
永远不要在子线程中直接调用 UI 组件的方法(如self.label.setText()
),必须通过信号传递到主线程处理。 -
信号线程安全:
PyQt5 的信号与槽机制是线程安全的,信号会自动排队到接收者所在线程执行。 -
内存管理:
- 子线程结束后,建议通过
finished.connect(deleteLater)
自动释放资源。 - 避免在子线程中持有对主线程对象的强引用,防止循环引用导致内存泄漏。
- 子线程结束后,建议通过
-
耗时任务拆分:
若任务特别长(如超过 10 秒),建议拆分为多个子步骤,通过信号定期汇报进度,避免界面长时间无响应。
十、实战案例:文件批量处理器
结合前面的知识点,实现一个简单的文件批量处理器,功能包括:
- 选择文件夹,显示其中所有文件。
- 勾选文件后,点击 “处理” 按钮,在子线程中批量重命名或修改文件内容。
- 显示处理进度和结果。
import sys
import os
from PyQt5.QtCore import QThread, pyqtSignal, QDir
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QListWidget, QListWidgetItem, QPushButton, QProgressBar,
QCheckBox, QMessageBox, QFileDialog
)
# ----------------------
# 文件处理子线程
# ----------------------
class FileProcessorThread(QThread):
progress_signal = pyqtSignal(int, str) # 进度(百分比)和状态消息
finish_signal = pyqtSignal(list) # 处理结果列表
def __init__(self, selected_files, operation):
super().__init__()
self.selected_files = selected_files # 选中的文件列表
self.operation = operation # 操作类型("rename" 或 "modify")
def run(self):
total = len(self.selected_files)
results = []
for idx, file_path in enumerate(self.selected_files):
# 模拟处理耗时(实际可替换为真实文件操作)
self.sleep(1) # 暂停 1 秒
# 计算进度百分比
progress = int((idx + 1) / total * 100)
status = f"处理中:{os.path.basename(file_path)}"
self.progress_signal.emit(progress, status) # 发送进度和状态
# 模拟操作(这里仅记录结果,不实际修改文件)
if self.operation == "rename":
result = f"重命名:{file_path} -> {file_path}_processed.txt"
else:
result = f"修改内容:{file_path}"
results.append(result)
self.finish_signal.emit(results) # 发送处理结果
# ----------------------
# 主窗口类
# ----------------------
class FileProcessorWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
self.selected_files = [] # 保存选中的文件路径
def initUI(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# 文件列表和勾选框
self.file_list = QListWidget()
self.file_list.setSelectionMode(QListWidget.ExtendedSelection) # 支持多选
layout.addWidget(self.file_list)
# 操作按钮栏
btn_layout = QHBoxLayout()
self.select_folder_btn = QPushButton("选择文件夹")
self.select_folder_btn.clicked.connect(self.select_folder)
self.process_btn = QPushButton("开始处理(重命名)")
self.process_btn.clicked.connect(lambda: self.start_process("rename"))
self.modify_btn = QPushButton("开始处理(修改内容)")
self.modify_btn.clicked.connect(lambda: self.start_process("modify"))
btn_layout.addWidget(self.select_folder_btn)
btn_layout.addWidget(self.process_btn)
btn_layout.addWidget(self.modify_btn)
layout.addLayout(btn_layout)
# 进度条和结果显示
self.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 100)
self.status_label = QLabel("等待选择文件...")
layout.addWidget(self.progress_bar)
layout.addWidget(self.status_label)
self.setWindowTitle("文件批量处理器")
self.setGeometry(100, 100, 600, 400)
def select_folder(self):
"""选择文件夹并加载文件列表"""
folder_path = QFileDialog.getExistingDirectory(self, "选择文件夹")
if not folder_path:
return
# 清空现有列表
self.file_list.clear()
self.selected_files = []
# 遍历文件夹中的文件(仅显示文件,不显示子文件夹)
dir = QDir(folder_path)
files = dir.entryInfoList(["*"], QDir.Files) # 筛选文件
for file_info in files:
item = QListWidgetItem(file_info.fileName())
item.setData(0, file_info.filePath()) # 存储文件路径到 item 的数据中
self.file_list.addItem(item)
self.status_label.setText(f"加载了 {len(files)} 个文件")
def start_process(self, operation):
"""启动文件处理任务"""
# 获取选中的文件项
selected_items = self.file_list.selectedItems()
if not selected_items:
QMessageBox.warning(self, "警告", "请先勾选要处理的文件!")
return
# 提取文件路径
self.selected_files = [item.data(0) for item in selected_items]
self.process_btn.setEnabled(False)
self.modify_btn.setEnabled(False)
self.status_label.setText("处理开始...")
self.progress_bar.setValue(0)
# 创建并启动子线程
self.worker = FileProcessorThread(self.selected_files, operation)
self.worker.progress_signal.connect(self.update_process_status)
self.worker.finish_signal.connect(self.process_completed)
self.worker.start()
def update_process_status(self, progress, status):
"""更新处理进度和状态"""
self.progress_bar.setValue(progress)
self.status_label.setText(status)
def process_completed(self, results):
"""处理完成后的回调"""
self.process_btn.setEnabled(True)
self.modify_btn.setEnabled(True)
self.progress_bar.setValue(100)
self.status_label.setText(f"处理完成!共处理 {len(results)} 个文件")
# 显示结果(这里用消息框展示,实际可优化为列表显示)
result_text = "\n".join(results)
QMessageBox.information(self, "处理结果", result_text)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = FileProcessorWindow()
window.show()
sys.exit(app.exec_())
功能说明:
-
选择文件夹:
点击 “选择文件夹” 按钮,加载该文件夹下的所有文件到列表,每个文件项附带文件路径数据。 -
勾选文件:
按住 Ctrl 或 Shift 键可多选文件,选中的文件将被批量处理。 -
处理任务:
- 点击 “重命名” 或 “修改内容” 按钮,创建子线程处理选中的文件。
- 子线程通过
progress_signal
实时汇报进度,主线程更新进度条和状态标签。 - 处理完成后,通过
finish_signal
返回结果列表,用消息框展示详细结果。
-
线程安全:
- 所有 UI 更新都通过信号与槽机制在主线程完成,确保线程安全。
- 处理过程中禁用操作按钮,防止重复提交任务。
十一、数据库操作:集成 SQLite
PyQt5 内置对 SQLite 的支持,适合开发中小型桌面应用。以下示例展示如何创建数据库、执行 CRUD 操作,并在界面中展示数据。
import sys
from PyQt5.QtSql import QSqlDatabase, QSqlTableModel, QSqlQuery
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QTableView, QPushButton, QVBoxLayout,
QWidget, QMessageBox, QLineEdit, QHBoxLayout, QLabel
)
class DatabaseApp(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
self.initDatabase()
def initUI(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# 顶部输入栏(添加新记录)
input_layout = QHBoxLayout()
input_layout.addWidget(QLabel("姓名:"))
self.name_input = QLineEdit()
input_layout.addWidget(self.name_input)
input_layout.addWidget(QLabel("年龄:"))
self.age_input = QLineEdit()
input_layout.addWidget(self.age_input)
self.add_btn = QPushButton("添加")
self.add_btn.clicked.connect(self.addRecord)
input_layout.addWidget(self.add_btn)
layout.addLayout(input_layout)
# 表格视图(展示数据)
self.table_view = QTableView()
layout.addWidget(self.table_view)
# 底部按钮栏
btn_layout = QHBoxLayout()
self.refresh_btn = QPushButton("刷新")
self.refresh_btn.clicked.connect(self.refreshTable)
btn_layout.addWidget(self.refresh_btn)
self.delete_btn = QPushButton("删除选中")
self.delete_btn.clicked.connect(self.deleteRecord)
btn_layout.addWidget(self.delete_btn)
layout.addLayout(btn_layout)
self.setWindowTitle("SQLite 数据库示例")
self.setGeometry(100, 100, 600, 400)
def initDatabase(self):
"""初始化数据库连接和表结构"""
# 创建数据库连接
self.db = QSqlDatabase.addDatabase("QSQLITE")
self.db.setDatabaseName("example.db")
if not self.db.open():
QMessageBox.critical(None, "数据库错误", f"无法连接数据库: {self.db.lastError().text()}")
sys.exit(1)
# 创建表(如果不存在)
query = QSqlQuery()
query.exec_(
"""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER
)
"""
)
# 初始化表格模型
self.model = QSqlTableModel()
self.model.setTable("users")
self.model.select()
# 设置表格视图
self.table_view.setModel(self.model)
self.table_view.setEditTriggers(QTableView.NoEditTriggers) # 禁止直接编辑
def addRecord(self):
"""添加新记录到数据库"""
name = self.name_input.text().strip()
age_text = self.age_input.text().strip()
if not name:
QMessageBox.warning(self, "警告", "姓名不能为空!")
return
try:
age = int(age_text) if age_text else None
except ValueError:
QMessageBox.warning(self, "警告", "年龄必须是数字!")
return
# 插入数据
query = QSqlQuery()
query.prepare("INSERT INTO users (name, age) VALUES (:name, :age)")
query.bindValue(":name", name)
query.bindValue(":age", age)
if query.exec_():
QMessageBox.information(self, "成功", "记录添加成功!")
self.refreshTable() # 刷新表格
self.name_input.clear()
self.age_input.clear()
else:
QMessageBox.critical(self, "错误", f"添加记录失败: {query.lastError().text()}")
def refreshTable(self):
"""刷新表格数据"""
self.model.select()
def deleteRecord(self):
"""删除选中的记录"""
selected_indexes = self.table_view.selectedIndexes()
if not selected_indexes:
QMessageBox.warning(self, "警告", "请先选择要删除的记录!")
return
# 获取选中行的 ID(假设 ID 在第一列)
row = selected_indexes[0].row()
id_index = self.model.index(row, 0) # 第一列是 ID
record_id = self.model.data(id_index)
# 确认删除
reply = QMessageBox.question(
self, "确认删除",
f"确定要删除 ID 为 {record_id} 的记录吗?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
# 删除记录
query = QSqlQuery()
query.prepare("DELETE FROM users WHERE id = :id")
query.bindValue(":id", record_id)
if query.exec_():
QMessageBox.information(self, "成功", "记录删除成功!")
self.refreshTable()
else:
QMessageBox.critical(self, "错误", f"删除记录失败: {query.lastError().text()}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = DatabaseApp()
window.show()
sys.exit(app.exec_())
关键功能说明:
-
数据库连接:
- 使用
QSqlDatabase.addDatabase("QSQLITE")
创建 SQLite 数据库连接。 QSqlQuery
用于执行 SQL 语句,包括创建表、插入数据、删除数据等。
- 使用
-
数据展示:
QSqlTableModel
作为表格数据模型,自动映射数据库表结构。QTableView
显示表格数据,设置为不可编辑模式(防止用户直接修改)。
-
增删操作:
- 添加记录时,使用预处理语句(
prepare()
和bindValue()
)防止 SQL 注入。 - 删除记录前,通过
selectedIndexes()
获取用户选择的行,并确认操作。
- 添加记录时,使用预处理语句(
十二、打包发布:将应用转换为可执行文件
完成开发后,需将 PyQt5 应用打包为独立可执行文件,方便分发。推荐使用 PyInstaller 或 Nuitka。
1. 使用 PyInstaller 打包
# 安装 PyInstaller
pip install pyinstaller
# 基本打包命令
pyinstaller --onefile --windowed your_script.py
# 参数说明:
# --onefile:打包为单个可执行文件
# --windowed:不显示命令行窗口(仅适用于 GUI 应用)
# --name:指定输出文件名(可选)
# --icon:指定应用图标(可选,需提供 .ico 文件)
2. 解决打包常见问题
-
缺少依赖:PyInstaller 有时无法自动检测所有依赖,需手动添加:
pyinstaller --onefile --windowed --hidden-import=module_name your_script.py
-
中文显示问题:
在代码中添加字体设置:font = QFont("SimHei") # 黑体 app.setFont(font)
-
大文件打包优化:
排除不必要的模块,减少包体积:pyinstaller --onefile --windowed --exclude-module=tkinter your_script.py
十三、性能优化与调试技巧
1. 调试技巧
-
打印调试信息:
使用print()
输出关键变量值,或使用logging
模块记录详细日志。 -
断点调试:
在 PyCharm 等 IDE 中设置断点,逐行调试代码。 -
异常捕获:
使用try-except
块捕获并处理异常,避免程序崩溃:try: # 可能出错的代码 result = 1 / 0 except Exception as e: QMessageBox.critical(self, "错误", f"操作失败: {str(e)}")
2. 性能优化
-
避免频繁刷新 UI:
大量数据更新时,使用setUpdatesEnabled(False)
临时禁用 UI 更新,完成后再启用。 -
使用线程处理耗时操作:
如前所述,将耗时任务放到子线程,避免阻塞主线程。 -
缓存机制:
对于频繁访问的数据,使用缓存减少重复计算:from functools import lru_cache @lru_cache(maxsize=32) # 缓存最近 32 次结果 def calculate_something(arg): # 复杂计算逻辑 return result
十四、高级主题:自定义组件与样式
1. 自定义组件(继承现有组件)
创建带图标的按钮组件:
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtGui import QIcon
class IconButton(QPushButton):
def __init__(self, icon_path, text="", parent=None):
super().__init__(text, parent)
self.setIcon(QIcon(icon_path))
self.setIconSize(24, 24) # 设置图标大小
self.setStyleSheet("padding: 5px;") # 设置内边距
2. 自定义样式(QSS)
使用 Qt Style Sheets 自定义组件外观:
# 设置全局样式
app.setStyleSheet("""
QMainWindow {
background-color: #f5f5f5;
}
QPushButton {
background-color: #4CAF50;
color: white;
border-radius: 5px;
padding: 8px 16px;
}
QPushButton:hover {
background-color: #45a049;
}
QLabel {
font-size: 14px;
}
""")
十五、学习资源推荐
-
官方文档:
-
书籍推荐:
- 《Python Qt GUI 快速编程》(Mark Summerfield)
- 《PyQt5 快速开发与实战》(董伟明)
-
在线教程:
-
社区与论坛:
总结
PyQt5 是一个功能强大的 GUI 框架,适合开发各种类型的桌面应用。