PyQt5 简明指南

这是一份简明的 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")

要传递参数,可以使用 lambdafunctools.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.threadself.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 类应处理缓慢的处理流程;辅助模块应保存可复用的解析、预处理或计算函数。

Leave a Reply