PyQt5 的使用
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, QLabelif __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, QVBoxLayoutclass 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 QTextEdittext_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 QStringListModelmodel = 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 QColorcolor = 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, QObjectclass 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) # 初始化 UIself.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 timetime.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, "警告", "姓名不能为空!")returntry:age = int(age_text) if age_text else Noneexcept 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) # 第一列是 IDrecord_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 QIconclass 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;}
""")
十五、学习资源推荐
-
官方文档:
- Qt5 官方文档
- PyQt5 官方文档
-
书籍推荐:
- 《Python Qt GUI 快速编程》(Mark Summerfield)
- 《PyQt5 快速开发与实战》(董伟明)
-
在线教程:
- zetcode.com 的 PyQt5 教程
- B 站 PyQt5 实战教程
-
社区与论坛:
- Qt 官方论坛:Home | Qt Forum
- Stack Overflow:https://stackoverflow.com/questions/tagged/pyqt5
总结
PyQt5 是一个功能强大的 GUI 框架,适合开发各种类型的桌面应用。