欢迎光临
我们一直在努力

【Python】GUI 开发从入门到实战(三):PyQt/PySide 进阶之路

引言:从 Tkinter 到 PyQt,一次质的飞跃

在前两篇文章中,我们学习了 Tkinter 的基础与实战。Tkinter 是 GUI 入门的绝佳选择,但当你需要开发功能复杂、界面专业、性能优异的桌面应用时,PyQt/PySide 才是真正的利器。

PyQt 和 PySide 都是 Qt 框架的 Python 绑定。Qt 是 C++ 编写的跨平台应用开发框架,在工业软件、科学计算、企业级应用领域拥有举足轻重的地位。两者的核心区别在于许可协议:PyQt 使用 GPL/commercial 许可-,而 PySide 使用更宽松的 LGPL-。在 API 层面,两者高度相似,掌握了其中一个,迁移到另一个几乎零成本。

本文将以 PyQt5 为主线进行讲解,同时标注 PySide6 的对应写法,方便你自由切换。

一、信号与槽机制:PyQt 的灵魂所在

1.1 理解信号与槽

信号与槽是 Qt 框架的核心通信机制,本质上是观察者模式的优雅实现。它不同于普通的函数调用——信号与槽之间是一种松耦合的通信方式。当按钮被点击时,它发出 clicked 信号,你只需将这个信号连接到一个函数(即“槽”),而不需要在按钮类里硬编码任何业务逻辑。

简单来说:

  • 信号(Signal) :事件发生的“通知”,比如“按钮被点击了”“滑块值改变了”

  • 槽(Slot) :响应信号的“动作”,比如“关闭窗口”“更新数据显示”

1.2 四大信号槽组合模式

根据信号的来源(内置/自定义)和槽的类型(内置/自定义),可以组合出四种模式:

模式一:内置信号 + 内置槽函数

这是最简单的用法,一行代码就能实现功能:

# 点击按钮关闭窗口——仅需一行代码!
btn.clicked.connect(self.close)

这种方式的优势在于零编码成本、由 Qt 官方维护、执行效率高(底层 C++ 直接调用)。

模式二:内置信号 + 自定义槽函数

当标准行为不够用时,你可以连接自定义函数来扩展功能:

def save_and_close(self):
self.save_data() # 扩展业务逻辑
self.close() # 复用内置功能

btn.clicked.connect(self.save_and_close)

设计要点在于功能分层——将业务逻辑与 UI 操作分离。

模式三:自定义信号 + 内置槽函数

这是实现深度解耦的关键模式。业务逻辑层不需要知道 UI 的存在,只需要发出信号:

class MyWidget(QWidget):
# 声明无参信号
data_processed = pyqtSignal()

def init_ui(self):
self.data_processed.connect(self.close)

def run_biz_logic(self):
if data_valid:
self.data_processed.emit() # 业务层触发信号,UI 自动响应

这种架构的优势在于:业务与 UI 解耦、可脱离 GUI 环境测试业务逻辑、信号可穿透父子组件层级进行跨组件通信。

模式四:自定义信号 + 自定义槽(动态数据流)

这是最强大的模式,适合多模块数据协同的场景:

# 声明带参数的信号
status_update = pyqtSignal(int, str)

# 定义槽函数(参数签名需匹配)
def update_ui(self, count, msg):
self.counter_label.setText(f"{count}")
self.log_view.append(msg)

# 连接并触发
self.status_update.connect(self.update_ui)
self.status_update.emit(10, "操作成功")

1.3 自定义信号详解

自定义信号需要满足以下条件:

  • 信号必须定义在 QObject 的子类中(大多数 PyQt 控件都满足)

  • 信号作为类属性声明,而非实例属性

  • 使用 pyqtSignal()(PyQt)或 Signal()(PySide) 创建

  • from PyQt5.QtCore import QObject, pyqtSignal

    class Worker(QObject):
    # 无参数信号
    started = pyqtSignal()

    # 带一个整数参数的信号
    progress = pyqtSignal(int)

    # 带多个参数的信号
    finished = pyqtSignal(int, str, dict)

    # 重载版本的信号(支持不同参数组合)
    value_changed = pyqtSignal([int], [str]) # 可发射 int 或 str

     关键陷阱:信号必须声明为类属性,而不能在函数内部声明为局部变量。局部信号会随函数销毁而失效:

    # ❌ 错误:局部信号会随函数销毁
    def init_ui(self):
    temp_signal = pyqtSignal() # 无效声明!

    # ✅ 正确:类级声明持久化
    class MyApp(QMainWindow):
    my_signal = pyqtSignal()

    1.4 @pyqtSlot / @Slot 装饰器

    在 PyQt5 中,可以使用 @pyqtSlot 装饰器来显式标记槽函数;在 PySide6 中对应的是 @Slot()。

    # PyQt5
    from PyQt5.QtCore import pyqtSlot

    @pyqtSlot(int, str)
    def on_update(self, value, text):
    print(f"收到: {value}, {text}")

    # PySide6
    from PySide6.QtCore import Slot

    @Slot(int, str)
    def on_update(self, value, text):
    print(f"收到: {value}, {text}")

    使用装饰器的好处包括:明确参数类型以提升性能(可节省约 10% 的调用开销)、帮助进行类型检查、使代码意图更清晰。

    1.5 信号槽调试技巧

    信号没响应时,可以从以下几个方面排查:

  • 生命周期问题:信号对象和槽对象是否被意外销毁?

  • 拼写错误:信号名是否写错了?

  • 参数类型不匹配:信号的参数类型是否与槽函数匹配?

  • 连接时机:connect() 是否在信号发射之前执行?

  • 快速验证方法:在 emit() 处加一行 print("信号已发射");或使用 QObject.receivers(signal) 检查当前有几个接收者。

    二、Qt Designer:可视化界面设计

    2.1 环境配置

    Qt Designer 是 PyQt 官方提供的可视化界面设计工具。安装 PyQt5 后,Qt Designer 通常位于 pyqt5-tools 包中:

    pip install pyqt5 pyqt5-tools

    在 PyCharm 中配置 Qt Designer 和 PyUIC 可以极大提升开发效率:

  • 打开 File > Settings > Tools > External Tools

  • 添加 Qt Designer:配置可执行文件路径

  • 添加 PyUIC:用于将 .ui 文件自动转换为 Python 代码

  • # PyUIC 配置参数
    Program: <Python解释器路径>
    Arguments: -m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
    Working directory: $FileDir$

    配置完成后,右键点击 .ui 文件选择 PyUIC,就能立即生成对应的 Python 界面代码。

    2.2 Qt Designer 核心功能

    Qt Designer 的控件系统远比表面看起来强大。除了常见的按钮、文本框等基础控件外,还有几种特殊的容器控件值得关注:

    容器控件用途
    QFrame 带边框的容器,用于视觉分组
    QTabWidget 标签页容器,实现多页面切换
    QStackedWidget 堆叠容器,适合向导式界面
    QDockWidget 可停靠窗口,可创建专业 IDE 风格界面

    布局管理是创建自适应界面的关键。Qt Designer 支持四种主要布局方式:

    布局类型特点适用场景
    QHBoxLayout 水平排列控件 工具栏、状态栏
    QVBoxLayout 垂直排列控件 表单、列表
    QGridLayout 网格排列 复杂表单、计算器
    QFormLayout 表单布局(标签+字段) 设置对话框

    2.3 两种开发方式的选择

    PyQt 支持两种开发方式:

    方式一:纯代码方式

    • 所有界面用 Python 代码构建

    • 适合需要动态生成界面的场景

    • 便于版本控制和代码审查

    方式二:可视化方式(推荐)

    • 使用 Qt Designer 拖拽生成 .ui 文件

    • 通过 pyuic5 转换为 Python 代码

    • 对新手非常友好,大幅提升开发效率

    实际项目中最常用的做法是:用 Qt Designer 设计界面,用 Python 代码编写业务逻辑,实现界面与逻辑的分离。

    三、QSS 样式表:让界面脱胎换骨

    3.1 QSS 基础语法

    Qt Style Sheets(QSS)是 PyQt 中用于控制界面外观的样式表语言,其语法与 Web 开发中的 CSS 非常相似。通过 QSS,你可以轻松实现:修改背景色、边框样式和圆角;调整字体、颜色和文本对齐方式;添加悬停、按下等交互状态效果;创建渐变、阴影等高级视觉效果。

    QSS 的基本规则是“选择器 + 声明块”:

    /* 类型选择器:应用到所有 QPushButton */
    QPushButton {
    background-color: #3498db;
    color: white;
    border: 2px solid #2980b9;
    border-radius: 8px;
    padding: 10px 20px;
    font-size: 14px;
    font-weight: bold;
    }

    /* ID 选择器:仅应用到 objectName 为 "loginBtn" 的按钮 */
    #loginBtn {
    background-color: #27ae60;
    }

    /* 伪状态:鼠标悬停时变色 */
    QPushButton:hover {
    background-color: #5dade2;
    }

    QPushButton:pressed {
    background-color: #1a5276;
    }

    3.2 选择器类型

    QSS 支持多种选择器类型:

    选择器类型语法示例说明
    类型选择器 控件类名 QPushButton 应用到所有该类型控件
    类选择器 .控件类名 .QPushButton 与类型选择器效果类似
    ID 选择器 #objectName #myButton 最精确,针对特定控件
    后代选择器 父控件 子控件 QDialog QPushButton 仅匹配特定父级下的控件

    3.3 如何应用 QSS

    在 PyQt 中应用 QSS 样式,通常有两种方式:

    方式一:为单个控件设置样式

    button = QPushButton("点击我")
    button.setStyleSheet("""
    QPushButton {
    background-color: #4CAF50;
    color: white;
    border-radius: 5px;
    padding: 8px 16px;
    }
    """)

    方式二:为整个应用程序设置全局样式(推荐)

    app = QApplication([])

    # 从 .qss 文件加载样式
    with open("style.qss", "r", encoding="utf-8") as f:
    app.setStyleSheet(f.read())

    # 或者直接使用字符串
    app.setStyleSheet("""
    QPushButton { background-color: #3498db; color: white; border-radius: 5px; }
    QLineEdit { border: 2px solid #bdc3c7; border-radius: 4px; padding: 5px; }
    QLabel { color: #2c3e50; font-size: 12px; }
    """)

    全局样式方便统一管理,但要注意选择器的特异性可能带来的样式冲突。

    3.4 使用第三方主题

    如果你不想从零开始写样式表,可以使用成熟的第三方主题:

    qdarkstyle:一套高质量的暗色主题,只需几行代码即可应用:

    pip install qdarkstyle
    import qdarkstyle
    app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())

    采用 QSS + 统一主题管理器的方案,是目前最成熟、最实用的界面美化方案。

    四、多线程编程:告别界面卡顿

    4.1 为什么需要多线程?

    在 PyQt GUI 应用中,所有 UI 操作必须在主线程执行。如果在主线程中执行耗时操作(如网络请求、文件读写、大量计算),界面会直接“卡死”——无法响应任何用户操作。

    解决方案是:将耗时操作放到工作线程中执行,通过信号与主线程通信来更新 UI。

    4.2 使用 QThread 实现多线程

    PyQt 提供了 QThread 类来创建后台工作线程。推荐的做法是继承 QThread 并重写 run() 方法:

    import time
    from PyQt5.QtCore import QThread, pyqtSignal

    class WorkerThread(QThread):
    # 定义信号,用于向主线程传递数据
    progress = pyqtSignal(int)
    finished = pyqtSignal(list)

    def run(self):
    """线程运行时执行"""
    data = []
    for i in range(100):
    time.sleep(0.05) # 模拟耗时操作
    data.append(i)
    self.progress.emit(i + 1) # 发射进度信号
    self.finished.emit(data) # 发射完成信号

    在主线程中使用:

    class MainWindow(QMainWindow):
    def __init__(self):
    super().__init__()
    # … UI 初始化 …
    self.thread = WorkerThread()
    self.thread.progress.connect(self.update_progress)
    self.thread.finished.connect(self.on_finished)

    def start_work(self):
    self.thread.start() # 启动线程

    def update_progress(self, value):
    self.progress_bar.setValue(value) # ✅ 在主线程更新 UI

    def on_finished(self, data):
    self.label.setText(f"完成!共 {len(data)} 条数据")

    4.3 关键规则与避坑指南

    规则一:工作线程绝对不能直接操作 UI 元素

    # ❌ 错误:在子线程中直接修改 UI
    def run(self):
    self.label.setText("完成") # 会导致崩溃!

    # ✅ 正确:通过信号让主线程更新 UI
    def run(self):
    self.finished.emit("完成") # 发射信号,主线程处理

    规则二:信号参数类型要谨慎

    传递复杂数据类型时,建议使用简单类型(int、str、list)或序列化后传递。嵌套容器(如深层字典)可能丢失类型信息。

    规则三:使用 @pyqtSlot 装饰器明确参数类型

    在多线程场景中,使用 @pyqtSlot 可以帮助 PyQt 更高效地处理跨线程信号传递。

    五、项目结构最佳实践

    当 PyQt 项目逐渐变大时,良好的项目结构至关重要。

    5.1 推荐的项目结构

    my_project/
    ├── main.py # 程序入口
    ├── requirements.txt # 依赖列表
    ├── src/
    │ ├── __init__.py
    │ ├── ui/ # 界面相关
    │ │ ├── __init__.py
    │ │ ├── main_window.py # 主窗口类
    │ │ └── main_window.ui # Qt Designer 文件
    │ ├── backend/ # 业务逻辑[reference:46]
    │ │ ├── __init__.py
    │ │ ├── worker.py # 工作线程
    │ │ └── data_manager.py # 数据管理
    │ └── resources/ # 静态资源[reference:47]
    │ ├── images/
    │ ├── styles/ # QSS 样式文件
    │ └── fonts/
    └── tests/ # 单元测试

    5.2 界面与逻辑分离的原则

    在实际开发中,应遵循 MVC(Model-View-Controller) 或类似的分层架构:

    • View(视图) :负责界面展示,由 Qt Designer 设计

    • Model(模型) :负责数据和业务逻辑

    • Controller(控制器) :负责协调 View 和 Model,通过信号与槽连接两者

    这样做的好处是:界面调整不影响业务逻辑、业务逻辑可以独立测试、多人协作时分工明确。

    六、实战项目:文件批量重命名工具

    现在,让我们综合运用所学知识,构建一个实用的文件批量重命名工具。

    6.1 功能规划

    • 选择文件夹,列出所有文件

    • 支持多种重命名规则(添加前缀/后缀、替换文本、序号命名)

    • 预览重命名结果

    • 一键执行重命名操作

    • 进度条显示执行进度(多线程)

    • 现代化界面风格(QSS 美化)

    6.2 完整代码

    import os
    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *

    # ============ 工作线程 ============
    class RenameWorker(QThread):
    progress = pyqtSignal(int)
    log = pyqtSignal(str)
    finished = pyqtSignal(bool, str)

    def __init__(self, folder_path, files, rule, rule_value):
    super().__init__()
    self.folder_path = folder_path
    self.files = files
    self.rule = rule
    self.rule_value = rule_value

    def run(self):
    try:
    total = len(self.files)
    for idx, old_name in enumerate(self.files):
    old_path = os.path.join(self.folder_path, old_name)

    # 根据规则生成新文件名
    name, ext = os.path.splitext(old_name)
    if self.rule == "prefix":
    new_name = self.rule_value + old_name
    elif self.rule == "suffix":
    new_name = name + self.rule_value + ext
    elif self.rule == "replace":
    old_str, new_str = self.rule_value.split("|", 1)
    new_name = old_name.replace(old_str, new_str)
    elif self.rule == "number":
    new_name = f"{self.rule_value}_{idx+1:03d}{ext}"
    else:
    new_name = old_name

    new_path = os.path.join(self.folder_path, new_name)

    # 如果新文件名已存在,跳过
    if os.path.exists(new_path) and old_path != new_path:
    self.log.emit(f"⚠️ 跳过 {old_name} → {new_name}(已存在)")
    continue

    os.rename(old_path, new_path)
    self.log.emit(f"✅ {old_name} → {new_name}")
    self.progress.emit(int((idx + 1) / total * 100))

    self.finished.emit(True, "重命名完成!")
    except Exception as e:
    self.finished.emit(False, f"错误:{str(e)}")

    # ============ 主窗口 ============
    class MainWindow(QMainWindow):
    def __init__(self):
    super().__init__()
    self.folder_path = ""
    self.files = []
    self.worker = None
    self.init_ui()
    self.apply_style()

    def init_ui(self):
    self.setWindowTitle("📁 批量文件重命名工具")
    self.setMinimumSize(700, 550)

    central = QWidget()
    self.setCentralWidget(central)
    main_layout = QVBoxLayout(central)

    # === 第一行:文件夹选择 ===
    folder_layout = QHBoxLayout()
    self.folder_label = QLabel("未选择文件夹")
    self.folder_label.setStyleSheet("border: 1px solid #bdc3c7; padding: 5px; border-radius: 4px;")
    self.folder_label.setMinimumHeight(30)
    self.folder_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

    browse_btn = QPushButton("📂 浏览")
    browse_btn.clicked.connect(self.browse_folder)

    folder_layout.addWidget(QLabel("文件夹:"))
    folder_layout.addWidget(self.folder_label)
    folder_layout.addWidget(browse_btn)
    main_layout.addLayout(folder_layout)

    # === 第二行:重命名规则 ===
    rule_layout = QHBoxLayout()
    rule_layout.addWidget(QLabel("规则:"))

    self.rule_combo = QComboBox()
    self.rule_combo.addItems(["添加前缀", "添加后缀", "替换文本", "序号命名"])
    self.rule_combo.currentIndexChanged.connect(self.on_rule_changed)
    rule_layout.addWidget(self.rule_combo)

    self.rule_entry = QLineEdit()
    self.rule_entry.setPlaceholderText("输入规则值…")
    rule_layout.addWidget(self.rule_entry)

    preview_btn = QPushButton("👁️ 预览")
    preview_btn.clicked.connect(self.preview)
    rule_layout.addWidget(preview_btn)

    execute_btn = QPushButton("🚀 执行")
    execute_btn.clicked.connect(self.execute)
    execute_btn.setStyleSheet("background-color: #27ae60; color: white; font-weight: bold;")
    rule_layout.addWidget(execute_btn)

    main_layout.addLayout(rule_layout)

    # === 第三行:进度条 ===
    self.progress_bar = QProgressBar()
    self.progress_bar.setValue(0)
    main_layout.addWidget(self.progress_bar)

    # === 第四行:文件列表 ===
    main_layout.addWidget(QLabel("文件列表:"))
    self.file_list = QListWidget()
    main_layout.addWidget(self.file_list)

    # === 第五行:日志 ===
    main_layout.addWidget(QLabel("日志:"))
    self.log_text = QTextEdit()
    self.log_text.setReadOnly(True)
    self.log_text.setMaximumHeight(120)
    main_layout.addWidget(self.log_text)

    # === 状态栏 ===
    self.statusBar().showMessage("就绪")

    def on_rule_changed(self, index):
    hints = ["输入前缀文本", "输入后缀文本", "输入 旧文本|新文本", "输入基础名称"]
    self.rule_entry.setPlaceholderText(hints[index])

    def browse_folder(self):
    folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
    if folder:
    self.folder_path = folder
    self.folder_label.setText(folder)
    self.load_files()

    def load_files(self):
    self.files = [f for f in os.listdir(self.folder_path)
    if os.path.isfile(os.path.join(self.folder_path, f))]
    self.file_list.clear()
    self.file_list.addItems(self.files)
    self.statusBar().showMessage(f"找到 {len(self.files)} 个文件")

    def preview(self):
    """预览重命名结果"""
    if not self.folder_path:
    QMessageBox.warning(self, "提示", "请先选择文件夹")
    return

    rule_idx = self.rule_combo.currentIndex()
    rules = ["prefix", "suffix", "replace", "number"]
    rule = rules[rule_idx]
    rule_value = self.rule_entry.text().strip()

    if not rule_value:
    QMessageBox.warning(self, "提示", "请输入规则值")
    return

    # 显示预览对话框
    preview_dialog = QDialog(self)
    preview_dialog.setWindowTitle("预览重命名结果")
    preview_dialog.resize(500, 400)

    layout = QVBoxLayout(preview_dialog)
    list_widget = QListWidget()

    for old_name in self.files[:100]: # 最多预览100个
    name, ext = os.path.splitext(old_name)
    if rule == "prefix":
    new_name = rule_value + old_name
    elif rule == "suffix":
    new_name = name + rule_value + ext
    elif rule == "replace":
    parts = rule_value.split("|", 1)
    if len(parts) == 2:
    new_name = old_name.replace(parts[0], parts[1])
    else:
    new_name = old_name
    else: # number
    new_name = f"{rule_value}_{self.files.index(old_name)+1:03d}{ext}"

    list_widget.addItem(f"{old_name} → {new_name}")

    layout.addWidget(list_widget)
    btn = QPushButton("关闭")
    btn.clicked.connect(preview_dialog.accept)
    layout.addWidget(btn)

    preview_dialog.exec_()

    def execute(self):
    if not self.folder_path or not self.files:
    QMessageBox.warning(self, "提示", "请先选择文件夹")
    return

    rule_idx = self.rule_combo.currentIndex()
    rules = ["prefix", "suffix", "replace", "number"]
    rule = rules[rule_idx]
    rule_value = self.rule_entry.text().strip()

    if not rule_value:
    QMessageBox.warning(self, "提示", "请输入规则值")
    return

    # 确认执行
    reply = QMessageBox.question(self, "确认执行",
    f"将重命名 {len(self.files)} 个文件,确认继续?",
    QMessageBox.Yes | QMessageBox.No)
    if reply != QMessageBox.Yes:
    return

    # 禁用控件
    self.setEnabled(False)
    self.progress_bar.setValue(0)
    self.log_text.clear()
    self.statusBar().showMessage("正在执行…")

    # 启动工作线程
    self.worker = RenameWorker(self.folder_path, self.files, rule, rule_value)
    self.worker.progress.connect(self.progress_bar.setValue)
    self.worker.log.connect(self.log_text.append)
    self.worker.finished.connect(self.on_worker_finished)
    self.worker.start()

    def on_worker_finished(self, success, message):
    self.setEnabled(True)
    if success:
    self.statusBar().showMessage("✅ " + message)
    self.load_files() # 刷新文件列表
    else:
    self.statusBar().showMessage("❌ " + message)
    QMessageBox.critical(self, "错误", message)

    def apply_style(self):
    """应用 QSS 样式"""
    self.setStyleSheet("""
    QMainWindow {
    background-color: #f5f6fa;
    }
    QPushButton {
    background-color: #3498db;
    color: white;
    border: none;
    border-radius: 4px;
    padding: 6px 14px;
    font-weight: bold;
    }
    QPushButton:hover {
    background-color: #2980b9;
    }
    QPushButton:pressed {
    background-color: #1a5276;
    }
    QLineEdit, QComboBox {
    border: 2px solid #dcdde1;
    border-radius: 4px;
    padding: 5px 8px;
    background-color: white;
    }
    QLineEdit:focus, QComboBox:focus {
    border-color: #3498db;
    }
    QListWidget, QTextEdit {
    border: 1px solid #dcdde1;
    border-radius: 4px;
    background-color: white;
    }
    QProgressBar {
    border: 1px solid #dcdde1;
    border-radius: 4px;
    text-align: center;
    }
    QProgressBar::chunk {
    background-color: #3498db;
    border-radius: 4px;
    }
    """)

    if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

    AI生成,效果如下:

    6.3 代码亮点解读

    1. 界面与逻辑分离:RenameWorker 线程类负责重命名逻辑,主窗口只负责界面交互和状态更新。

    2. 多线程保障流畅性:重命名操作在工作线程中执行,通过信号实时更新进度和日志,界面始终保持响应。

    3. QSS 美化:通过全局样式表为所有控件提供了现代化的视觉风格——圆角、悬停效果、焦点状态等。

    4. 预览功能:在实际执行前让用户预览重命名结果,避免误操作。

    5. 健壮性处理:检查文件是否已存在、异常捕获、操作确认对话框等。

    七、PyQt vs PySide:如何选择?

    对比维度PyQt5PySide6 (Qt for Python)
    许可协议 GPL / 商业 LGPL
    更新频率 较慢 较快,与 Qt 官方同步
    API 风格 与 PySide 高度相似 与 PyQt 高度相似
    信号定义 pyqtSignal Signal
    槽装饰器 @pyqtSlot @Slot()
    社区生态 更成熟,资料更多 增长中,官方支持

    选择建议:

    • 商业闭源项目 → PySide6(LGPL 更友好)

    • 开源项目 → 两者皆可

    • 注重资料丰富度 → PyQt5(教程和案例更多)

    • 追求最新 Qt 特性 → PySide6

    总结

    本篇是系列文章的第三篇,我们深入学习了 PyQt/PySide 的核心进阶技能:

    • 信号与槽机制:四种组合模式、自定义信号、装饰器用法

    • Qt Designer:可视化界面设计、PyUIC 代码生成

    • QSS 样式表:语法规则、选择器类型、全局样式应用

    • 多线程编程:QThread 使用、线程安全通信

    • 项目结构:最佳实践与分层架构

    • 完整实战:批量文件重命名工具

    至此,你已经掌握了从 Tkinter 入门到 PyQt 进阶的完整 GUI 开发技能体系。

    赞(0)
    未经允许不得转载:171主机测评 » 【Python】GUI 开发从入门到实战(三):PyQt/PySide 进阶之路
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址