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 框架,适合开发各种类型的桌面应用。
相关文章:
PyQt5 的使用
PyQt5 是 Python 里基于 Qt 框架的 GUI 开发工具,能做桌面应用,跨平台(Windows/macOS/Linux 都能用)。你可能想知道:怎么开始用?有哪些核心组件?怎么写界面逻辑?别急,咱们…...

JavaScript【6】事件
1.概述: 在 JavaScript 中,事件(Event)是浏览器或 DOM(文档对象模型)与 JavaScript 代码之间交互的一种机制。它代表了在浏览器环境中发生的特定行为或者动作,比如用户点击鼠标、敲击键盘、页面…...

STM32F10xx 参考手册
6. 什么是寄存器 本章参考资料:《STM32F10xx 参考手册》、《STM32F10xx数据手册》、 学习本章时,配合《STM32F10xx 参考手册》“存储器和总线架构”及“通用I/O(GPIO)”章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。…...
使用Docker部署Nacos
sudo systemctl start docker sudo systemctl enable docker docker --version 步骤 2: 拉取 Nacos Docker 镜像 拉取 Nacos 镜像: 你可以从 Docker Hub 上拉取官方的 Nacos 镜像,使用以下命令: docker pull nacos/nacos-server 这会从 …...
深度学习中ONNX格式的模型文件
一、模型部署的核心步骤 模型部署的完整流程通常分为以下阶段,用 “跨国旅行” 类比: 步骤类比解释技术细节1. 训练模型学会一门语言(如中文)用 PyTorch/TensorFlow 训练模型2. 导出为 ONNX翻译成国际通用语言(如英语…...

TIFS2024 | CRFA | 基于关键区域特征攻击提升对抗样本迁移性
Improving Transferability of Adversarial Samples via Critical Region-Oriented Feature-Level Attack 摘要-Abstract引言-Introduction相关工作-Related Work提出的方法-Proposed Method问题分析-Problem Analysis扰动注意力感知加权-Perturbation Attention-Aware Weighti…...

Redis 发布订阅模式深度解析:原理、应用与实践
在现代分布式系统架构中,实时消息传递机制扮演着至关重要的角色。Redis 作为一款高性能的内存数据库,其内置的发布订阅(Pub/Sub)功能提供了一种轻量级、高效的消息通信方案。本文将全面剖析 Redis 发布订阅模式,从其基本概念、工作原理到实际…...
环形缓冲区 ring buffer 概述
环形缓冲区 ring buffer 概述 1. 简介 环形缓冲区(ring buffer),是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。也称作环形缓冲区(circular buffer),环形队列&…...

飞帆控件 post or get it when it has get
我在这里分享两个链接: post_get_it 设计 - 飞帆 有人看出来这个控件是干什么用吗? 控件的配置:...

SQL里where条件的顺序影响索引使用吗?
大家好,我是锋哥。今天分享关于【SQL里where条件的顺序影响索引使用吗?】面试题。希望对大家有帮助; SQL里where条件的顺序影响索引使用吗? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 SQL 查询中,W…...

SAP学习笔记 - 开发豆知识02 - com.sap.cds.services.cds.CdsService 废止,那么用什么代替呢?
我看很多人代码里面用的都是这个CdsService类,可以自从2.0版本往上这个类就没了啊。 它的代替是什么呢?用的CqnService 那么怎么查的呢? 我也是下载好几个包,解压看,然后发现这里还可以直接看,挺方便的。…...

OpenResty 深度解析:构建高性能 Web 服务的终极方案
引言 openresty是什么?在我个人对它的理解来看相当于嵌入了lua的nginx; 我们在nginx中嵌入lua是为了不需要再重新编译,我们只需要重新修改lua脚本,随后重启即可; 一.lua指令序列 我们分别从初始化阶段,重写/访问阶段,内容阶段,日志…...

什么是路由器环回接口?
路由器环回接口(LoopbackInterface)是网络设备中的一种逻辑虚拟接口,不依赖物理硬件,但在网络配置和管理中具有重要作用。以下是其核心要点: 一、基本特性 1.虚拟性与稳定性 环回接口是纯软件实现的逻辑接口&#x…...
OpenHarmony:开源操作系统重塑产业数字化底座
OpenHarmony:开源操作系统重塑产业数字化底座 引言:当操作系统成为数字公共品 在万物智联时代,操作系统不再是科技巨头的专属领地。华为捐赠的OpenHarmony项目,正以开源协作模式重构操作系统产业格局。这个脱胎于商业版本的开源…...

【MySQL进阶】如何在ubuntu下安装MySQL数据库
前言 🌟🌟本期讲解关于如何在ubuntu环境下安装mysql的详细介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 dz…...

【数据结构】_二叉树
1.二叉树链式结构的实现 1.1 前置说明 在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树&#x…...

给图表组件上点“颜色” —— 我与 CodeBuddy 的合作记录
我正在参加CodeBuddy「首席试玩官」内容创作大赛,本文所使用的 CodeBuddy 免费下载链接:腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 前段时间,我在开发一个 Vue3 项目的时候,碰到了一个小小的挑战:我想做一个可…...

使用 YOLO 结合 PiscTrace 实现股票走势图像识别
在智能投研和金融分析中,自动识别图表中的模式(如 K 线走势、支撑/阻力位、形态结构)成为一种新兴手段。传统的技术分析依赖大量人工判断,而计算机视觉技术的发展,特别是 YOLO 模型在图像识别领域的高效表现࿰…...

OpenCV中的光流估计方法详解
文章目录 一、引言二、核心算法原理1. 光流法基本概念2. 算法实现步骤 三、代码实现详解1. 初始化设置2. 特征点检测3. 光流计算与轨迹绘制 四、实际应用效果五、优化方向六、结语 一、引言 在计算机视觉领域,运动目标跟踪是一个重要的研究方向,广泛应用…...
OpenCL C++ 常见属性与函数
核心对象与属性 对象/属性描述示例cl::Platform表示OpenCL平台cl::Platform::get(&platforms)cl::Device表示计算设备cl::Device::getDefault()cl::Context管理设备、内存和命令队列的上下文cl::Context(contextDevices)cl::CommandQueue命令队列,用于提交命令cl::Command…...
Android核心系统服务:AMS、WMS、PMS 与 system_server 进程解析
1. 引言 在 Android 系统中,ActivityManagerService (AMS)、WindowManagerService (WMS) 和 PackageManagerService (PMS) 是三个最核心的系统服务,它们分别管理着应用的生命周期、窗口显示和应用包管理。 但你是否知道,这些服务并不是独立…...
18.自动化生成知识图谱的多维度质量评估方法论
文章目录 一、结构维度评估1.1 拓扑结构评估1.1.1 基础图论指标1.1.2 层级结构指标 1.2 逻辑一致性评估1.2.1 形式逻辑验证1.2.2 约束满足度 二、语义维度评估2.1 语义一致性评估2.1.1 标签语义分析2.1.2 关系语义评估 2.2 语义表示质量2.2.1 嵌入质量2.2.2 上下文语义评估 三、…...
【行为型之命令模式】游戏开发实战——Unity可撤销系统与高级输入管理的架构秘钥
文章目录 ⌨️ 命令模式(Command Pattern)深度解析一、模式本质与核心价值二、经典UML结构三、Unity实战代码(可撤销的建造系统)1. 定义命令接口与接收者2. 实现具体命令3. 命令管理器(Invoker)4. 客户端使…...
图论模板(部分)
图论模板(部分) maincpp #include <iostream> #include <climits> #include <limits>typedef unsigned long long ull; typedef long long ll; typedef long double ld; typedef std::pair<int, int> PII;#define rep(i, n) f…...
LeetCode 热题 100_寻找重复数(100_287_中等_C++)(技巧)(暴力解法;哈希集合;二分查找)
LeetCode 热题 100_寻找重复数(100_287_中等_C) 题目描述:输入输出样例:题解:解题思路:思路一(暴力解法):思路二(哈希集合):思路三&am…...

NBA足球赛事直播源码体育直播M33模板赛事源码
源码名称:体育直播赛事扁平自适应M33直播模板源码 开发环境:帝国cms7.5 空间支持:phpmysql 带软件采集,可以挂着自动采集发布,无需人工操作! 演示地址:NBA足球赛事直播源码体育直播M33模板赛事…...
【QT 项目部署指南】使用 Inno Setup 打包 QT 程序为安装包(超详细图文教程)
一、为什么需要打包成安装包? 在完成 QT 项目开发后,直接发布可执行文件(.exe)和依赖的 DLL 文件虽然可行,但存在以下问题: 用户体验差:用户需手动管理文件路径,容易因文件缺失导致…...

电子电器架构 --- 整车造车阶段四个重要节点
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…...

黑马点评-用户登录
文章目录 用户登录发送短信验证码注册/登录校验登录 用户登录 发送短信验证码 public Result sendCode(String phone, HttpSession session) {// 1.校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机…...
ecmascript 第6版特性 ECMA-262 ES6
https://blog.csdn.net/zlpzlpzyd/article/details/146125018 在之前写的文章基础上,ES6在export和import的基础外,还有如下特性 特性说明let/const块级作用域变量声明>箭头函数Promise异步编程...