这是一份简明的 PyQt5 指南,涵盖常见 GUI 任务:转换 Qt Designer 文件、创建可滚动布局、连接信号、修改应用图标、调整标签页顺序,以及将耗时任务移入 QThread。
从 .ui 文件生成 .py 文件
如果界面是在 Qt Designer 中设计的,可以用 pyuic 将 .ui 文件转换为 Python:
python -m PyQt5.uic.pyuic -x [FILENAME].ui -o [FILENAME].py
-x 选项会添加一小段可直接运行的测试代码。如果生成的文件只会被其他模块导入,则省略 -x:
python -m PyQt5.uic.pyuic [FILENAME].ui -o [FILENAME].py
一个实用的项目结构如下:
project/
├── main.py
├── ui_main.py # 由 .ui 生成
├── workers.py # QThread/Worker 代码
└── resources/
└── icon.png
将生成的 UI 文件与应用逻辑分开。如果 .ui 文件发生变化,重新生成 ui_main.py,不要手动编辑它。
让控件可滚动
要让大型表单或结果面板可滚动,可以把内容控件放进 QScrollArea:
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QScrollArea, QLabel
content = QWidget()
layout = QVBoxLayout(content)
for i in range(100):
layout.addWidget(QLabel(f"Row {i}"))
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setWidget(content)
如果使用 Qt Designer,添加一个 QScrollArea,在其中放置一个子控件,并把实际布局设置在这个子控件上。滚动区域本身通常应启用 widgetResizable。
修改外观并设置任务栏图标
在主窗口上使用 setWindowTitle() 和 setWindowIcon()。在显示窗口之前设置应用图标:
import sys
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QMainWindow
app = QApplication(sys.argv)
app.setWindowIcon(QIcon("resources/icon.png"))
window = QMainWindow()
window.setWindowTitle("PyQt5 Application")
window.setWindowIcon(QIcon("resources/icon.png"))
window.show()
sys.exit(app.exec_())
简单样式可以使用样式表:
window.setStyleSheet("""
QPushButton {
padding: 6px 10px;
}
QLineEdit {
padding: 4px;
}
""")
除非项目有专门的主题文件,否则样式表应保持精简。
连接信号
信号通过 .connect() 连接到槽:
self.button.clicked.connect(self.run_task)
槽可以是任何可调用对象:
def run_task(self):
print("Button clicked")
要传递参数,可以使用 lambda 或 functools.partial:
self.button.clicked.connect(lambda: self.open_file("data.txt"))
如果按钮会被动态重新配置,应先断开过时的处理函数,再连接新的处理函数:
try:
self.button.clicked.disconnect()
except TypeError:
pass
self.button.clicked.connect(self.new_handler)
在没有连接任何信号时调用 disconnect() 会引发 TypeError,因此当连接状态不确定时需要捕获它。
使用 pyqtSignal
自定义信号声明为类属性:
from PyQt5.QtCore import QObject, pyqtSignal
class Worker(QObject):
progress = pyqtSignal(int)
result = pyqtSignal(str)
failed = pyqtSignal(str)
一个信号可以发出多个值:
class Worker(QObject):
finished = pyqtSignal(str, int)
类型列表遵循以下模式:
pyqtSignal(type1, type2, ...)
对于多个值,优先直接发出结构化值,而不是依赖全局变量:
self.finished.emit("done", 100)
元组也可以作为一个对象发出:
summary = pyqtSignal(tuple)
self.summary.emit((filename, count, elapsed_seconds))
使用 QThread 运行耗时任务
不要在按钮处理函数中直接运行缓慢任务。这样会阻塞事件循环并冻结 GUI。应将工作移入在独立 QThread 中运行的 worker 对象。
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
class Worker(QObject):
progress = pyqtSignal(int)
finished = pyqtSignal(str)
failed = pyqtSignal(str)
@pyqtSlot()
def run(self):
try:
for i in range(101):
# Do part of the long-running task here.
self.progress.emit(i)
self.finished.emit("Task complete")
except Exception as exc:
self.failed.emit(str(exc))
def start_worker(self):
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.progress.connect(self.progress_bar.setValue)
self.worker.finished.connect(self.on_worker_finished)
self.worker.failed.connect(self.on_worker_failed)
self.worker.finished.connect(self.thread.quit)
self.worker.failed.connect(self.thread.quit)
self.thread.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.start()
保留对 self.thread 和 self.worker 的引用。如果它们只是局部变量,Python 可能会在任务仍在运行时对其进行垃圾回收。
修改标签页顺序
对于 QTabWidget,使用 removeTab() 和 insertTab():
index = self.tabs.indexOf(self.settings_tab)
widget = self.tabs.widget(index)
label = self.tabs.tabText(index)
icon = self.tabs.tabIcon(index)
self.tabs.removeTab(index)
self.tabs.insertTab(0, widget, icon, label)
self.tabs.setCurrentWidget(widget)
如果标签页是在 Qt Designer 中创建的,通常在那里重新排序会更简单。当顺序取决于用户设置或运行时状态时,再使用代码处理。
模块化预处理和控制逻辑
一个可维护的 PyQt 项目会分离 UI 设置、预处理和控制逻辑:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.connect_signals()
def connect_signals(self):
self.ui.runButton.clicked.connect(self.run_preprocessing)
def run_preprocessing(self):
options = self.collect_options()
self.start_worker(options)
def collect_options(self):
return {
"input": self.ui.inputLineEdit.text(),
"enabled": self.ui.enableCheckBox.isChecked(),
}
这样可以让生成的 UI 代码随时可重新生成,并让业务逻辑保持可测试。主窗口应负责协调界面;worker 类应处理缓慢的处理流程;辅助模块应保存可复用的解析、预处理或计算函数。
