【Python桌面应用】PySide6 界面开发完全指南

文章目录

    • 1. 引言
    • 2. PySide6 简介与安装
      • 2.1 什么是PySide6
      • 2.2 PySide6 vs. PyQt6
      • 2.3 安装PySide6
      • 2.4 开发环境配置建议
    • 3. Qt 设计原理
      • 3.1 Qt对象模型
      • 3.2 信号与槽机制
      • 3.3 Qt坐标系统
      • 3.4 Qt样式表(QSS)
    • 4. 创建第一个应用
      • 4.1 基本应用结构
      • 4.2 主窗口与应用生命周期
      • 4.3 使用面向对象方法重构
      • 4.4 简单交互示例
    • 5. 常用控件详解
      • 5.1 按钮与输入控件
        • 5.1.1 QPushButton (按钮)
        • 5.1.2 QLabel (标签)
        • 5.1.3 QLineEdit (单行文本框)
        • 5.1.4 QTextEdit (多行文本编辑器)
        • 5.1.5 QCheckBox (复选框)
        • 5.1.6 QRadioButton (单选按钮)
        • 5.1.7 QComboBox (下拉列表)
        • 5.1.8 QSlider (滑块)
        • 5.1.9 QSpinBox (数字输入)
      • 5.2 容器控件
        • 5.2.1 QGroupBox (分组框)
        • 5.2.2 QTabWidget (选项卡)
        • 5.2.3 QScrollArea (滚动区域)
        • 5.2.4 QStackedWidget (堆叠部件)
      • 5.3 对话框
        • 5.3.1 QMessageBox (消息框)
        • 5.3.2 QFileDialog (文件对话框)
        • 5.3.3 QInputDialog (输入对话框)
        • 5.3.4 QColorDialog (颜色对话框)
        • 5.3.5 QFontDialog (字体对话框)
      • 5.4 菜单与工具栏
        • 5.4.1 QMenuBar 和 QMenu (菜单栏和菜单)
        • 5.4.2 QToolBar (工具栏)
        • 5.4.3 QStatusBar (状态栏)
    • 6. 布局管理系统
      • 6.1 QVBoxLayout (垂直布局)
      • 6.2 QHBoxLayout (水平布局)
      • 6.3 QGridLayout (网格布局)
      • 6.4 QFormLayout (表单布局)
      • 6.5 嵌套布局
      • 6.6 高级布局技巧
        • 6.6.1 布局与控件的尺寸策略
        • 6.6.2 间隔器的使用
        • 6.6.3 边距和间距
        • 6.6.4 对齐方式
    • 7. 信号与槽机制
      • 7.1 基础概念
      • 7.2 信号种类与发射
        • 7.2.1 内置信号
        • 7.2.2 自定义信号
      • 7.3 槽的种类与连接
        • 7.3.1 函数槽
        • 7.3.2 方法槽
        • 7.3.3 Lambda表达式
        • 7.3.4 部分函数
      • 7.4 信号连接管理
        • 7.4.1 断开连接
        • 7.4.2 阻塞信号
        • 7.4.3 连接类型
      • 7.5 实例:自定义信号与槽
    • 8. 样式与主题定制
      • 8.1 基本样式属性
      • 8.2 Qt样式表(QSS)
      • 8.3 常用样式属性
        • 8.3.1 背景与前景
        • 8.3.2 边框与轮廓
        • 8.3.3 文本格式
        • 8.3.4 尺寸与布局
      • 8.4 状态相关样式
      • 8.5 自定义应用主题
      • 8.6 动态切换主题
    • 9. Qt Designer 可视化设计
      • 9.1 Qt Designer 基础
      • 9.2 创建界面
      • 9.3 加载.ui文件
        • 9.3.1 使用QUiLoader
        • 9.3.2 使用uic编译
      • 9.4 多界面管理
      • 9.5 表单继承
      • 9.6 资源文件管理
        • 9.6.1 创建资源文件
        • 9.6.2 编译资源文件
        • 9.6.3 使用资源
    • 10. 多线程与并发处理
      • 10.1 QThread 基本用法
      • 10.2 QRunnable 和线程池
    • 11. 数据持久化
      • 11.1 QSettings
      • 11.2 SQLite数据库集成
    • 12. 高级图形与动画
      • 12.1 自定义绘图
      • 12.2 动画效果
    • 13. 国际化与本地化
      • 13.1 使用QTranslator
      • 13.2 使用tr()进行文本标记
    • 14. 实战项目开发
      • 14.1 应用架构设计
      • 14.2 项目质量保证
    • 15. 打包与发布
      • 15.1 使用PyInstaller打包
      • 15.2 使用cx_Freeze打包
    • 16. 总结

1. 引言

在Python应用程序开发领域,图形用户界面(GUI)是提升用户体验的关键因素。PySide6作为Qt框架的Python绑定,为开发者提供了强大而灵活的GUI开发工具。本文将全面介绍PySide6的核心概念、组件和最佳实践,帮助读者快速掌握这一现代GUI开发技术。

无论你是GUI开发新手,还是想从其他GUI框架迁移到PySide6,本指南都将为你提供系统化的学习路径,帮助你构建专业、美观且功能强大的Python桌面应用。

2. PySide6 简介与安装

2.1 什么是PySide6

PySide6是Qt for Python项目的一部分,它提供了对Qt 6.0的官方Python绑定。Qt是一个跨平台的C++应用程序开发框架,以其丰富的UI组件、优秀的性能和跨平台能力而闻名。PySide6继承了Qt的这些优点,同时结合了Python的简洁性和开发效率。

PySide6的主要特点包括:

  • 跨平台兼容性:支持Windows、macOS、Linux等多种操作系统
  • 丰富的UI组件库:提供200多种现成的UI控件
  • 信号与槽机制:独特的事件处理方式
  • 强大的图形能力:支持2D/3D图形渲染
  • 内置的国际化支持:轻松实现多语言界面
  • Qt Designer集成:可视化界面设计工具

2.2 PySide6 vs. PyQt6

PySide6和PyQt6都是Qt的Python绑定,功能几乎相同,主要区别在于许可证:

  • PySide6:使用更为宽松的LGPL许可证
  • PyQt6:使用GPL许可证,商业应用可能需要购买商业许可

代码方面,两者差异很小,通常只需修改导入语句就能在两者间切换。

2.3 安装PySide6

使用pip安装PySide6非常简单:

pip install pyside6

验证安装是否成功:

import PySide6.QtCore
print(PySide6.__version__)
print(PySide6.QtCore.__version__)

对于Anaconda用户,也可以使用conda安装:

conda install -c conda-forge pyside6

2.4 开发环境配置建议

推荐的PySide6开发环境:

  • Python:3.6+(推荐3.8+)
  • IDE
    • PyCharm(专业版提供更好的Qt支持)
    • Visual Studio Code(配合Python和Qt相关插件)
  • 调试工具:Qt Designer(界面设计)和Qt Creator(完整IDE)
  • 版本控制:Git(特别是处理.ui文件时)

3. Qt 设计原理

3.1 Qt对象模型

Qt对象模型是理解PySide6的基础,它基于以下几个核心概念:

  • QObject:几乎所有Qt类的基类,提供信号与槽机制的基础
  • 元对象系统:提供运行时类型信息、动态属性系统等功能
  • 属性系统:允许在对象上动态添加属性
  • 事件系统:处理用户交互和系统事件

例如,一个基本的QObject派生类:

from PySide6.QtCore import QObject, Signal, Slot

class MyObject(QObject):
    # 定义信号
    valueChanged = Signal(int)
    
    def __init__(self):
        super().__init__()
        self._value = 0
    
    # 定义槽
    @Slot(int)
    def setValue(self, value):
        if self._value != value:
            self._value = value
            self.valueChanged.emit(value)

3.2 信号与槽机制

信号与槽是Qt最独特的特性之一,它提供了一种类型安全的回调机制:

  • 信号(Signal):对象发出的通知,表示某些事件已经发生
  • 槽(Slot):响应信号的函数或方法

信号与槽的连接:

from PySide6.QtWidgets import QApplication, QPushButton

app = QApplication([])
button = QPushButton("Click me")

# 连接按钮的clicked信号到自定义函数
def onButtonClicked():
    print("Button clicked!")

button.clicked.connect(onButtonClicked)
button.show()
app.exec()

3.3 Qt坐标系统

Qt使用自己的坐标系统,原点(0,0)通常位于组件的左上角:

  • 物理坐标:实际设备上的像素位置
  • 逻辑坐标:考虑DPI缩放的虚拟坐标
  • 设备独立像素:跨平台一致性的关键

3.4 Qt样式表(QSS)

Qt样式表类似于CSS,用于自定义UI外观:

button = QPushButton("Styled Button")
button.setStyleSheet("""
    QPushButton {
        background-color: #4CAF50;
        border: none;
        color: white;
        padding: 8px 16px;
        border-radius: 4px;
        font-size: 14px;
    }
    QPushButton:hover {
        background-color: #45a049;
    }
    QPushButton:pressed {
        background-color: #3d8b40;
    }
""")

4. 创建第一个应用

4.1 基本应用结构

所有PySide6应用都遵循类似的基本结构:

import sys
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout

# 1. 创建应用实例
app = QApplication(sys.argv)

# 2. 创建主窗口
window = QWidget()
window.setWindowTitle("My First PySide6 App")
window.setGeometry(100, 100, 400, 200)  # x, y, width, height

# 3. 创建布局和组件
layout = QVBoxLayout()
label = QLabel("Hello, PySide6!")
layout.addWidget(label)
window.setLayout(layout)

# 4. 显示窗口
window.show()

# 5. 执行应用
sys.exit(app.exec())

4.2 主窗口与应用生命周期

应用的生命周期管理是GUI程序的重要部分:

  • 初始化:创建QApplication实例和主窗口
  • 事件循环:app.exec()启动事件循环,处理用户输入和系统事件
  • 终止:事件循环结束,应用关闭

4.3 使用面向对象方法重构

更实用的做法是将应用窗口封装为类:

import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QVBoxLayout, QWidget

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("OOP PySide6 App")
        self.setGeometry(100, 100, 400, 200)
        self.setupUI()
        
    def setupUI(self):
        # 创建中央部件和布局
        central_widget = QWidget()
        layout = QVBoxLayout(central_widget)
        
        # 添加控件
        label = QLabel("Hello from OOP PySide6 App!")
        layout.addWidget(label)
        
        # 设置中央部件
        self.setCentralWidget(central_widget)

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

4.4 简单交互示例

添加按钮和事件响应,创建一个简单的交互应用:

import sys
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QLabel, 
    QVBoxLayout, QWidget, QPushButton
)
from PySide6.QtCore import Qt

class CounterApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Counter App")
        self.setGeometry(100, 100, 300, 200)
        
        # 计数器初始值
        self.counter = 0
        
        # 设置UI
        self.setupUI()
        
    def setupUI(self):
        # 中央部件和布局
        central_widget = QWidget()
        layout = QVBoxLayout(central_widget)
        
        # 显示计数的标签
        self.label = QLabel(f"Count: {self.counter}")
        self.label.setAlignment(Qt.AlignCenter)
        layout.addWidget(self.label)
        
        # 增加按钮
        increment_button = QPushButton("Increment")
        increment_button.clicked.connect(self.increment)
        layout.addWidget(increment_button)
        
        # 重置按钮
        reset_button = QPushButton("Reset")
        reset_button.clicked.connect(self.reset)
        layout.addWidget(reset_button)
        
        # 设置中央部件
        self.setCentralWidget(central_widget)
    
    def increment(self):
        self.counter += 1
        self.label.setText(f"Count: {self.counter}")
    
    def reset(self):
        self.counter = 0
        self.label.setText(f"Count: {self.counter}")

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

这个简单的计数器应用展示了PySide6的基本功能:窗口创建、控件布局、事件处理和状态管理。

5. 常用控件详解

PySide6提供了丰富的控件库,以下是最常用的控件及其用法。

5.1 按钮与输入控件

5.1.1 QPushButton (按钮)

最基本的交互控件:

from PySide6.QtWidgets import QPushButton

# 创建按钮
button = QPushButton("Click Me")

# 设置快捷键
button.setShortcut("Ctrl+C")

# 连接点击事件
button.clicked.connect(on_button_clicked)

# 设置按钮图标
from PySide6.QtGui import QIcon
button.setIcon(QIcon("path/to/icon.png"))
5.1.2 QLabel (标签)

用于显示文本或图像:

from PySide6.QtWidgets import QLabel
from PySide6.QtGui import QPixmap
from PySide6.QtCore import Qt

# 文本标签
text_label = QLabel("Hello World")
text_label.setAlignment(Qt.AlignCenter)  # 文本居中

# 图像标签
image_label = QLabel()
pixmap = QPixmap("image.jpg")
image_label.setPixmap(pixmap.scaled(200, 200, Qt.KeepAspectRatio))
5.1.3 QLineEdit (单行文本框)

用户文本输入控件:

from PySide6.QtWidgets import QLineEdit

# 创建文本框
text_input = QLineEdit()
text_input.setPlaceholderText("Enter your name")  # 设置占位文本
text_input.setMaxLength(50)  # 设置最大长度

# 获取文本
def get_text():
    print(text_input.text())

# 文本变化信号
text_input.textChanged.connect(lambda text: print(f"Text changed: {text}"))
5.1.4 QTextEdit (多行文本编辑器)

用于多行文本输入和显示:

from PySide6.QtWidgets import QTextEdit

# 创建多行文本编辑器
text_editor = QTextEdit()
text_editor.setPlaceholderText("Write your message here...")

# 设置和获取文本
text_editor.setText("Initial text")
content = text_editor.toPlainText()  # 获取纯文本
html_content = text_editor.toHtml()  # 获取HTML格式内容

# 文本变化信号
text_editor.textChanged.connect(lambda: print("Text changed"))
5.1.5 QCheckBox (复选框)

用于布尔选择:

from PySide6.QtWidgets import QCheckBox

# 创建复选框
checkbox = QCheckBox("Enable feature")
checkbox.setChecked(True)  # 默认选中

# 状态变化信号
checkbox.stateChanged.connect(lambda state: print(f"State: {state}"))
5.1.6 QRadioButton (单选按钮)

用于互斥选择:

from PySide6.QtWidgets import QRadioButton, QButtonGroup, QVBoxLayout, QWidget

# 需要用QButtonGroup组织多个单选按钮
group = QButtonGroup()

# 创建单选按钮
radio1 = QRadioButton("Option 1")
radio2 = QRadioButton("Option 2")
radio3 = QRadioButton("Option 3")

# 添加到按钮组
group.addButton(radio1, 1)  # 第二个参数是ID
group.addButton(radio2, 2)
group.addButton(radio3, 3)

# 默认选中
radio1.setChecked(True)

# 信号连接
group.buttonClicked.connect(lambda button: print(f"Selected: {button.text()}"))
5.1.7 QComboBox (下拉列表)

用于从预定义列表中选择:

from PySide6.QtWidgets import QComboBox

# 创建下拉列表
combo = QComboBox()
combo.addItem("Option 1")
combo.addItem("Option 2")
combo.addItem("Option 3")

# 带数据的项
combo.addItem("Red", "#FF0000")
combo.addItem("Green", "#00FF00")
combo.addItem("Blue", "#0000FF")

# 获取选择
def get_selection():
    index = combo.currentIndex()
    text = combo.currentText()
    data = combo.currentData()
    print(f"Selected: {text} at index {index} with data {data}")

# 选择变化信号
combo.currentIndexChanged.connect(get_selection)
5.1.8 QSlider (滑块)

用于范围值选择:

from PySide6.QtWidgets import QSlider
from PySide6.QtCore import Qt

# 创建水平滑块
slider = QSlider(Qt.Horizontal)
slider.setMinimum(0)
slider.setMaximum(100)
slider.setValue(50)  # 初始值
slider.setTickPosition(QSlider.TicksBelow)  # 显示刻度
slider.setTickInterval(10)  # 刻度间隔

# 值变化信号
slider.valueChanged.connect(lambda value: print(f"Value: {value}"))
5.1.9 QSpinBox (数字输入)

用于数字输入:

from PySide6.QtWidgets import QSpinBox

# 创建数字输入框
spin = QSpinBox()
spin.setMinimum(0)
spin.setMaximum(100)
spin.setValue(50)  # 初始值
spin.setSingleStep(5)  # 步长
spin.setPrefix("$")  # 前缀
spin.setSuffix(" units")  # 后缀

# 值变化信号
spin.valueChanged.connect(lambda value: print(f"Value: {value}"))

5.2 容器控件

5.2.1 QGroupBox (分组框)

用于组织相关控件:

from PySide6.QtWidgets import QGroupBox, QVBoxLayout, QRadioButton

# 创建分组框
group_box = QGroupBox("Select Option")

layout = QVBoxLayout()

# 添加内容
radio1 = QRadioButton("Option 1")
radio2 = QRadioButton("Option 2")
layout.addWidget(radio1)
layout.addWidget(radio2)

# 设置布局
group_box.setLayout(layout)
5.2.2 QTabWidget (选项卡)

创建选项卡界面:

from PySide6.QtWidgets import QTabWidget, QWidget, QVBoxLayout, QLabel

# 创建选项卡部件
tabs = QTabWidget()

# 创建第一个选项卡
tab1 = QWidget()
tab1_layout = QVBoxLayout(tab1)
tab1_layout.addWidget(QLabel("This is the first tab"))

# 创建第二个选项卡
tab2 = QWidget()
tab2_layout = QVBoxLayout(tab2)
tab2_layout.addWidget(QLabel("This is the second tab"))

# 添加选项卡
tabs.addTab(tab1, "Tab 1")
tabs.addTab(tab2, "Tab 2")

# 选项卡切换信号
tabs.currentChanged.connect(lambda index: print(f"Tab switched to: {index}"))
5.2.3 QScrollArea (滚动区域)

为大型内容提供滚动功能:

from PySide6.QtWidgets import QScrollArea, QWidget, QVBoxLayout, QLabel

# 创建滚动区域
scroll = QScrollArea()
scroll.setWidgetResizable(True)  # 允许内容部件调整大小

# 创建内容部件
content = QWidget()
layout = QVBoxLayout(content)

# 添加大量内容
for i in range(100):
    layout.addWidget(QLabel(f"Item {i}"))

# 设置内容部件
scroll.setWidget(content)
5.2.4 QStackedWidget (堆叠部件)

在同一区域显示多个部件,但一次只显示一个:

from PySide6.QtWidgets import QStackedWidget, QWidget, QVBoxLayout, QLabel, QPushButton

# 创建堆叠部件
stack = QStackedWidget()

# 创建页面
page1 = QWidget()
page1_layout = QVBoxLayout(page1)
page1_layout.addWidget(QLabel("This is page 1"))

page2 = QWidget()
page2_layout = QVBoxLayout(page2)
page2_layout.addWidget(QLabel("This is page 2"))

# 添加页面
stack.addWidget(page1)
stack.addWidget(page2)

# 切换页面
stack.setCurrentIndex(0)  # 显示第一个页面

# 切换按钮
def switch_page():
    current = stack.currentIndex()
    stack.setCurrentIndex(1 - current)  # 在0和1之间切换

button = QPushButton("Switch Page")
button.clicked.connect(switch_page)

5.3 对话框

5.3.1 QMessageBox (消息框)

用于显示消息和获取用户响应:

from PySide6.QtWidgets import QMessageBox

# 信息对话框
def show_info():
    QMessageBox.information(
        None,  # 父窗口
        "Information",  # 标题
        "Operation completed successfully.",  # 消息
        QMessageBox.Ok  # 按钮
    )

# 警告对话框
def show_warning():
    QMessageBox.warning(
        None,
        "Warning",
        "This action might be dangerous.",
        QMessageBox.Ok | QMessageBox.Cancel
    )

# 错误对话框
def show_error():
    QMessageBox.critical(
        None,
        "Error",
        "A critical error has occurred.",
        QMessageBox.Ok
    )

# 问题对话框
def ask_question():
    result = QMessageBox.question(
        None,
        "Confirmation",
        "Are you sure you want to proceed?",
        QMessageBox.Yes | QMessageBox.No
    )
    
    if result == QMessageBox.Yes:
        print("User confirmed")
    else:
        print("User cancelled")
5.3.2 QFileDialog (文件对话框)

用于文件选择:

from PySide6.QtWidgets import QFileDialog

# 打开文件对话框
def open_file():
    file_path, _ = QFileDialog.getOpenFileName(
        None,  # 父窗口
        "Open File",  # 标题
        "",  # 起始目录
        "Text Files (*.txt);;All Files (*)"  # 文件过滤器
    )
    
    if file_path:
        print(f"Selected file: {file_path}")

# 保存文件对话框
def save_file():
    file_path, _ = QFileDialog.getSaveFileName(
        None,
        "Save File",
        "",
        "Text Files (*.txt);;All Files (*)"
    )
    
    if file_path:
        print(f"Save to: {file_path}")

# 选择目录对话框
def select_directory():
    directory = QFileDialog.getExistingDirectory(
        None,
        "Select Directory"
    )
    
    if directory:
        print(f"Selected directory: {directory}")

# 多文件选择
def select_multiple_files():
    file_paths, _ = QFileDialog.getOpenFileNames(
        None,
        "Select Files",
        "",
        "Images (*.png *.jpg);;All Files (*)"
    )
    
    if file_paths:
        print(f"Selected {len(file_paths)} files")
5.3.3 QInputDialog (输入对话框)

用于获取用户输入:

from PySide6.QtWidgets import QInputDialog

# 获取文本输入
def get_text_input():
    text, ok = QInputDialog.getText(
        None,  # 父窗口
        "Input",  # 标题
        "Enter your name:"  # 提示
    )
    
    if ok and text:
        print(f"User entered: {text}")

# 获取数字输入
def get_int_input():
    number, ok = QInputDialog.getInt(
        None,
        "Input",
        "Enter your age:",
        25,  # 默认值
        0,   # 最小值
        120  # 最大值
    )
    
    if ok:
        print(f"User entered: {number}")

# 获取下拉选择
def get_item_selection():
    items = ["Red", "Green", "Blue", "Yellow"]
    item, ok = QInputDialog.getItem(
        None,
        "Select Color",
        "Choose your favorite color:",
        items,
        0,  # 默认选择索引
        False  # 是否可编辑
    )
    
    if ok and item:
        print(f"User selected: {item}")
5.3.4 QColorDialog (颜色对话框)

用于颜色选择:

from PySide6.QtWidgets import QColorDialog
from PySide6.QtGui import QColor

# 颜色选择对话框
def choose_color():
    color = QColorDialog.getColor(
        QColor(255, 0, 0),  # 初始颜色
        None,  # 父窗口
        "Select Color"  # 标题
    )
    
    if color.isValid():
        print(f"Selected color: RGB({color.red()}, {color.green()}, {color.blue()})")
        print(f"Hex: {color.name()}")
5.3.5 QFontDialog (字体对话框)

用于字体选择:

from PySide6.QtWidgets import QFontDialog
from PySide6.QtGui import QFont

# 字体选择对话框
def choose_font():
    initial_font = QFont("Arial", 12)
    font, ok = QFontDialog.getFont(
        initial_font,  # 初始字体
        None,  # 父窗口
        "Select Font"  # 标题
    )
    
    if ok:
        print(f"Selected font: {font.family()}, {font.pointSize()}pt")
        if font.bold():
            print("Font is bold")
        if font.italic():
            print("Font is italic")

5.4 菜单与工具栏

5.4.1 QMenuBar 和 QMenu (菜单栏和菜单)

创建应用菜单:

from PySide6.QtWidgets import QMainWindow, QMenuBar, QMenu, QAction
from PySide6.QtGui import QIcon, QKeySequence

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Menu Example")
        self.setGeometry(100, 100, 800, 600)
        
        # 创建菜单栏
        menu_bar = self.menuBar()
        
        # 创建文件菜单
        file_menu = menu_bar.addMenu("&File")
        
        # 创建新建动作
        new_action = QAction(QIcon("new.png"), "&New", self)
        new_action.setShortcut(QKeySequence("Ctrl+N"))
        new_action.setStatusTip("Create a new file")
        new_action.triggered.connect(self.new_file)
        file_menu.addAction(new_action)
        
        # 创建打开动作
        open_action = QAction("&Open...", self)
        open_action.setShortcut(QKeySequence("Ctrl+O"))
        open_action.triggered.connect(self.open_file)
        file_menu.addAction(open_action)
        
        # 添加分隔符
        file_menu.addSeparator()
        
        # 创建退出动作
        exit_action = QAction("E&xit", self)
        exit_action.setShortcut(QKeySequence("Ctrl+Q"))
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)
        
        # 创建编辑菜单
        edit_menu = menu_bar.addMenu("&Edit")
        
        # 添加子菜单
        format_menu = edit_menu.addMenu("&Format")
        format_menu.addAction("&Bold")
        format_menu.addAction("&Italic")
        
    def new_file(self):
        print("New file")
        
    def open_file(self):
        print("Open file")
5.4.2 QToolBar (工具栏)

创建工具栏:

from PySide6.QtWidgets import QMainWindow, QToolBar, QAction
from PySide6.QtGui import QIcon

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Toolbar Example")
        
        # 创建工具栏
        toolbar = QToolBar("Main Toolbar")
        self.addToolBar(toolbar)
        
        # 添加动作
        new_action = QAction(QIcon("new.png"), "New", self)
        new_action.triggered.connect(self.new_file)
        toolbar.addAction(new_action)
        
        open_action = QAction(QIcon("open.png"), "Open", self)
        open_action.triggered.connect(self.open_file)
        toolbar.addAction(open_action)
        
        # 添加分隔符
        toolbar.addSeparator()
        
        # 添加可选择的动作
        bold_action = QAction(QIcon("bold.png"), "Bold", self)
        bold_action.setCheckable(True)  # 可选择的
        bold_action.toggled.connect(lambda checked: print(f"Bold: {checked}"))
        toolbar.addAction(bold_action)
        
    def new_file(self):
        print("New file")
        
    def open_file(self):
        print("Open file")
5.4.3 QStatusBar (状态栏)

添加状态栏:

from PySide6.QtWidgets import QMainWindow, QStatusBar, QLabel

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("StatusBar Example")
        
        # 创建状态栏
        status_bar = QStatusBar()
        self.setStatusBar(status_bar)
        
        # 添加永久消息
        status_bar.addPermanentWidget(QLabel("Ready"))
        
        # 显示临时消息(5秒)
        status_bar.showMessage("Loading...", 5000)

6. 布局管理系统

布局管理是GUI应用中非常重要的部分,PySide6提供了多种布局管理器来组织控件,使界面在不同大小的窗口下保持合理的排列。

6.1 QVBoxLayout (垂直布局)

控件从上到下垂直排列:

from PySide6.QtWidgets import QWidget, QVBoxLayout, QPushButton

widget = QWidget()
layout = QVBoxLayout(widget)  # 直接设置父部件

# 添加控件
layout.addWidget(QPushButton("Button 1"))
layout.addWidget(QPushButton("Button 2"))
layout.addWidget(QPushButton("Button 3"))

# 添加间距
layout.addSpacing(20)  # 添加20像素的垂直间距

# 设置控件之间的间距
layout.setSpacing(10)  # 所有控件之间的间距为10像素

# 设置边距
layout.setContentsMargins(10, 10, 10, 10)  # 左, 上, 右, 下

6.2 QHBoxLayout (水平布局)

控件从左到右水平排列:

from PySide6.QtWidgets import QWidget, QHBoxLayout, QPushButton

widget = QWidget()
layout = QHBoxLayout(widget)

# 添加控件
layout.addWidget(QPushButton("Left"))
layout.addWidget(QPushButton("Center"))
layout.addWidget(QPushButton("Right"))

# 控制拉伸因子
# 当窗口大小变化时,各控件获得不同比例的空间
layout = QHBoxLayout()
layout.addWidget(QPushButton("Small"), 1)  # 拉伸因子为1
layout.addWidget(QPushButton("Medium"), 2)  # 拉伸因子为2
layout.addWidget(QPushButton("Large"), 3)  # 拉伸因子为3

6.3 QGridLayout (网格布局)

控件在二维网格中排列:

from PySide6.QtWidgets import QWidget, QGridLayout, QPushButton, QLabel, QLineEdit

widget = QWidget()
layout = QGridLayout(widget)

# 添加控件 - addWidget(widget, row, column, rowSpan, columnSpan)
layout.addWidget(QLabel("Name:"), 0, 0)  # 第0行,第0列
layout.addWidget(QLineEdit(), 0, 1)      # 第0行,第1列

layout.addWidget(QLabel("Email:"), 1, 0)  # 第1行,第0列
layout.addWidget(QLineEdit(), 1, 1)       # 第1行,第1列

# 跨越多行或多列
layout.addWidget(QPushButton("Submit"), 2, 0, 1, 2)  # 第2行,跨越2列

6.4 QFormLayout (表单布局)

专门用于表单的布局管理器,自动处理标签和字段的对齐:

from PySide6.QtWidgets import QWidget, QFormLayout, QLineEdit, QSpinBox, QComboBox

widget = QWidget()
layout = QFormLayout(widget)

# 添加行 - addRow(label, field)
layout.addRow("Name:", QLineEdit())
layout.addRow("Age:", QSpinBox())
layout.addRow("Country:", QComboBox())

# 使用已有控件作为标签
label = QLabel("Email:")
field = QLineEdit()
layout.addRow(label, field)

# 仅添加标签
layout.addRow("Contact Information")

# 仅添加控件
layout.addRow(QPushButton("Submit"))

6.5 嵌套布局

布局可以嵌套,创建复杂的界面:

from PySide6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, 
    QPushButton, QTextEdit, QLabel
)

class NestedLayoutExample(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Nested Layouts")
        self.setGeometry(100, 100, 600, 400)
        
        # 主布局
        main_layout = QVBoxLayout(self)
        
        # 上部分:标题
        main_layout.addWidget(QLabel("Message Composer"))
        
        # 中部分:编辑区域
        main_layout.addWidget(QTextEdit())
        
        # 下部分:按钮行(水平布局)
        button_layout = QHBoxLayout()
        
        button_layout.addWidget(QPushButton("Save Draft"))
        button_layout.addWidget(QPushButton("Discard"))
        button_layout.addStretch(1)  # 添加可拉伸的空间,使按钮靠左对齐
        button_layout.addWidget(QPushButton("Send"))
        
        # 将按钮布局添加到主布局
        main_layout.addLayout(button_layout)

6.6 高级布局技巧

6.6.1 布局与控件的尺寸策略

控制控件在布局中的大小行为:

from PySide6.QtWidgets import QPushButton, QSizePolicy

button = QPushButton("Resizable Button")

# 设置尺寸策略
button.setSizePolicy(
    QSizePolicy.Expanding,  # 水平策略
    QSizePolicy.Fixed       # 垂直策略
)

# 可用的尺寸策略:
# - QSizePolicy.Fixed: 控件具有固定大小
# - QSizePolicy.Minimum: 控件的最小大小提示是其最小尺寸
# - QSizePolicy.Maximum: 控件的最大大小提示是其最大尺寸
# - QSizePolicy.Preferred: 控件可以扩展,但没有必要
# - QSizePolicy.Expanding: 控件应该优先扩展
# - QSizePolicy.MinimumExpanding: 控件应该扩展,但可以缩小到最小大小
# - QSizePolicy.Ignored: 忽略控件的大小提示
6.6.2 间隔器的使用

使用弹性间隔器控制控件的位置:

from PySide6.QtWidgets import QHBoxLayout, QPushButton, QWidget

widget = QWidget()
layout = QHBoxLayout(widget)

# 添加一个按钮靠左
layout.addWidget(QPushButton("Left"))

# 添加可拉伸空间
layout.addStretch(1)

# 添加一个按钮靠右
layout.addWidget(QPushButton("Right"))

# 结果:按钮将分别靠左和靠右,中间有弹性空间
6.6.3 边距和间距

调整布局的边距和间距:

from PySide6.QtWidgets import QVBoxLayout

layout = QVBoxLayout()

# 设置所有边距(左、上、右、下)
layout.setContentsMargins(10, 20, 10, 20)

# 设置控件之间的间距
layout.setSpacing(15)
6.6.4 对齐方式

控制控件在布局中的对齐方式:

from PySide6.QtWidgets import QHBoxLayout, QPushButton
from PySide6.QtCore import Qt

layout = QHBoxLayout()

# 创建按钮
button = QPushButton("Aligned Button")

# 添加控件时指定对齐方式
layout.addWidget(button, 0, Qt.AlignTop | Qt.AlignLeft)

# 可用的对齐标志:
# - Qt.AlignLeft: 水平左对齐
# - Qt.AlignRight: 水平右对齐
# - Qt.AlignHCenter: 水平居中对齐
# - Qt.AlignTop: 垂直顶部对齐
# - Qt.AlignBottom: 垂直底部对齐
# - Qt.AlignVCenter: 垂直居中对齐
# - Qt.AlignCenter: 水平和垂直都居中对齐

7. 信号与槽机制

信号与槽是Qt/PySide6最为独特的机制,它实现了组件之间的解耦通信,是理解和掌握PySide6开发的核心。

7.1 基础概念

信号与槽的基本概念:

  • 信号(Signal):在特定事件发生时发出的通知
  • 槽(Slot):接收信号的函数或方法
  • 连接(Connection):将信号连接到槽,建立事件响应关系
from PySide6.QtWidgets import QApplication, QPushButton

app = QApplication([])
button = QPushButton("Click Me")

# 信号:button的clicked信号
# 槽:print_message函数
def print_message():
    print("Button clicked!")

# 连接:将clicked信号连接到print_message槽
button.clicked.connect(print_message)

button.show()
app.exec()

7.2 信号种类与发射

7.2.1 内置信号

PySide6控件都有预定义的信号:

# 常见控件的内置信号
button.clicked.connect(handler)  # 按钮被点击
checkbox.stateChanged.connect(handler)  # 复选框状态变化
spinbox.valueChanged.connect(handler)  # 数值改变
lineedit.textChanged.connect(handler)  # 文本改变
slider.valueChanged.connect(handler)  # 滑块值改变
7.2.2 自定义信号

在自定义类中定义信号:

from PySide6.QtCore import QObject, Signal

class Counter(QObject):
    # 定义一个无参数的信号
    countChanged = Signal()
    
    # 定义带一个int参数的信号
    valueChanged = Signal(int)
    
    # 定义带多个参数的信号
    rangeChanged = Signal(int, int)
    
    # 定义带不同类型参数的多个重载
    dataChanged = Signal([int], [str])
    
    def __init__(self):
        super().__init__()
        self._value = 0
    
    def setValue(self, value):
        if self._value != value:
            self._value = value
            # 发射信号
            self.valueChanged.emit(value)
            self.countChanged.emit()

使用自定义信号:

# 创建实例
counter = Counter()

# 连接信号
counter.valueChanged.connect(lambda val: print(f"Value changed to: {val}"))
counter.countChanged.connect(lambda: print("Count changed"))

# 触发信号
counter.setValue(10)  # 输出 "Value changed to: 10" 和 "Count changed"

7.3 槽的种类与连接

7.3.1 函数槽

使用普通函数作为槽:

def handle_click():
    print("Button clicked")

button.clicked.connect(handle_click)
7.3.2 方法槽

使用类方法作为槽:

class MyWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.button = QPushButton("Click Me")
        self.button.clicked.connect(self.handle_click)
        
        layout = QVBoxLayout(self)
        layout.addWidget(self.button)
    
    def handle_click(self):
        print("Button clicked in class context")
7.3.3 Lambda表达式

使用Lambda表达式进行简单的信号处理:

# 无参数
button.clicked.connect(lambda: print("Clicked!"))

# 有参数
spinbox.valueChanged.connect(lambda value: print(f"Value: {value}"))

# 调用其他函数并传递额外参数
slider.valueChanged.connect(lambda value: self.updateValue(value, "slider"))
7.3.4 部分函数

使用functools.partial创建部分应用的函数:

from functools import partial

def handle_value(source, value):
    print(f"Value from {source}: {value}")

# 连接到部分应用的函数
slider1.valueChanged.connect(partial(handle_value, "slider1"))
slider2.valueChanged.connect(partial(handle_value, "slider2"))

7.4 信号连接管理

7.4.1 断开连接

断开信号与槽的连接:

# 断开特定槽
button.clicked.disconnect(specific_handler)

# 断开所有连接到该信号的槽
button.clicked.disconnect()
7.4.2 阻塞信号

临时阻止信号发射:

# 阻塞所有信号
widget.blockSignals(True)

# 进行一些不需要触发信号的操作
widget.setValue(100)

# 恢复信号
widget.blockSignals(False)
7.4.3 连接类型

Qt提供了不同类型的信号连接:

from PySide6.QtCore import Qt

# 默认连接 - 如果信号和槽在同一线程,直接调用;否则排队
button.clicked.connect(handler)

# 直接连接 - 信号发射时立即调用槽,无论线程
button.clicked.connect(handler, Qt.DirectConnection)

# 队列连接 - 总是将调用排队,即使在同一线程
button.clicked.connect(handler, Qt.QueuedConnection)

# 阻塞队列连接 - 如果在不同线程,将阻塞直到槽返回
button.clicked.connect(handler, Qt.BlockingQueuedConnection)

7.5 实例:自定义信号与槽

创建一个带有自定义信号与槽的温度转换器:

from PySide6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout,
    QLabel, QDoubleSpinBox, QComboBox
)
from PySide6.QtCore import Signal, Slot

class TemperatureConverter(QWidget):
    # 定义信号
    temperatureChanged = Signal(float, str)
    
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Temperature Converter")
        
        self.initUI()
        self.setupConnections()
        
    def initUI(self):
        main_layout = QVBoxLayout(self)
        
        # 创建输入布局
        input_layout = QHBoxLayout()
        
        self.temp_spinbox = QDoubleSpinBox()
        self.temp_spinbox.setRange(-273.15, 1000000)
        self.temp_spinbox.setValue(0)
        self.temp_spinbox.setDecimals(2)
        
        self.unit_combo = QComboBox()
        self.unit_combo.addItems(["Celsius", "Fahrenheit", "Kelvin"])
        
        input_layout.addWidget(self.temp_spinbox)
        input_layout.addWidget(self.unit_combo)
        
        main_layout.addLayout(input_layout)
        
        # 创建输出布局
        self.result_layout = QVBoxLayout()
        self.celsius_label = QLabel("0.00 °C")
        self.fahrenheit_label = QLabel("32.00 °F")
        self.kelvin_label = QLabel("273.15 K")
        
        self.result_layout.addWidget(self.celsius_label)
        self.result_layout.addWidget(self.fahrenheit_label)
        self.result_layout.addWidget(self.kelvin_label)
        
        main_layout.addLayout(self.result_layout)
    
    def setupConnections(self):
        # 连接控件信号到自定义槽
        self.temp_spinbox.valueChanged.connect(self.onTemperatureInput)
        self.unit_combo.currentTextChanged.connect(self.onUnitChanged)
        
        # 连接自定义信号到自定义槽
        self.temperatureChanged.connect(self.updateTemperatures)
    
    @Slot(float)
    def onTemperatureInput(self, value):
        unit = self.unit_combo.currentText()
        self.temperatureChanged.emit(value, unit)
    
    @Slot(str)
    def onUnitChanged(self, unit):
        value = self.temp_spinbox.value()
        self.temperatureChanged.emit(value, unit)
    
    @Slot(float, str)
    def updateTemperatures(self, value, unit):
        if unit == "Celsius":
            celsius = value
            fahrenheit = celsius * 9/5 + 32
            kelvin = celsius + 273.15
        elif unit == "Fahrenheit":
            fahrenheit = value
            celsius = (fahrenheit - 32) * 5/9
            kelvin = celsius + 273.15
        elif unit == "Kelvin":
            kelvin = value
            celsius = kelvin - 273.15
            fahrenheit = celsius * 9/5 + 32
        
        self.celsius_label.setText(f"{celsius:.2f} °C")
        self.fahrenheit_label.setText(f"{fahrenheit:.2f} °F")
        self.kelvin_label.setText(f"{kelvin:.2f} K")

if __name__ == '__main__':
    app = QApplication([])
    window = TemperatureConverter()
    window.show()
    app.exec()

这个示例展示了:

  1. 创建自定义信号(temperatureChanged)
  2. 使用@Slot装饰器定义槽函数
  3. 连接内置控件信号到自定义槽
  4. 连接自定义信号到自定义槽
  5. 在槽函数中处理业务逻辑

8. 样式与主题定制

PySide6提供了多种方式来定制应用的外观和风格,从简单的样式调整到完整的主题定制。

8.1 基本样式属性

通过样式表(QSS)为单个控件设置样式:

from PySide6.QtWidgets import QPushButton

button = QPushButton("Styled Button")

# 设置单一样式属性
button.setStyleSheet("background-color: #4CAF50; color: white;")

# 多行样式
button.setStyleSheet("""
    background-color: #4CAF50;
    color: white;
    border: none;
    padding: 8px 16px;
    font-size: 14px;
""")

8.2 Qt样式表(QSS)

QSS类似于CSS,允许使用选择器和属性定义样式:

from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget

app = QApplication([])
window = QMainWindow()
central_widget = QWidget()
layout = QVBoxLayout(central_widget)

# 添加一些按钮
normal_button = QPushButton("Normal Button")
danger_button = QPushButton("Danger Button")
danger_button.setObjectName("dangerButton")
success_button = QPushButton("Success Button")
success_button.setProperty("type", "success")

layout.addWidget(normal_button)
layout.addWidget(danger_button)
layout.addWidget(success_button)

window.setCentralWidget(central_widget)

# 应用样式表
window.setStyleSheet("""
    /* 默认按钮样式 */
    QPushButton {
        background-color: #e0e0e0;
        border: none;
        padding: 8px 16px;
        color: #333;
        font-weight: bold;
        border-radius: 4px;
    }
    
    QPushButton:hover {
        background-color: #d0d0d0;
    }
    
    QPushButton:pressed {
        background-color: #c0c0c0;
    }
    
    /* 通过对象名选择器 */
    QPushButton#dangerButton {
        background-color: #f44336;
        color: white;
    }
    
    QPushButton#dangerButton:hover {
        background-color: #d32f2f;
    }
    
    /* 通过属性选择器 */
    QPushButton[type="success"] {
        background-color: #4CAF50;
        color: white;
    }
    
    QPushButton[type="success"]:hover {
        background-color: #388E3C;
    }
""")

window.show()
app.exec()

8.3 常用样式属性

8.3.1 背景与前景
/* 背景颜色 */
background-color: #f0f0f0;

/* 背景图像 */
background-image: url(background.png);
background-repeat: no-repeat;
background-position: center;

/* 文本颜色 */
color: #333333;
8.3.2 边框与轮廓
/* 边框 */
border: 1px solid #999999;
border-width: 1px;
border-style: solid;
border-color: #999999;

/* 特定边的边框 */
border-left: 2px dashed red;
border-top: 1px dotted blue;

/* 圆角 */
border-radius: 4px;
border-top-left-radius: 8px;
8.3.3 文本格式
/* 字体 */
font-family: "Arial", sans-serif;
font-size: 14px;
font-weight: bold;

/* 文本对齐 */
text-align: center;

/* 文本装饰 */
text-decoration: underline;
8.3.4 尺寸与布局
/* 尺寸 */
width: 100px;
min-width: 50px;
max-width: 200px;
height: 30px;

/* 内边距 */
padding: 8px;
padding-left: 16px;

/* 外边距 */
margin: 5px;
margin-top: 10px;

8.4 状态相关样式

为控件的不同状态定义样式:

/* 基本状态 */
QPushButton {
    background-color: #e0e0e0;
}

/* 悬停状态 */
QPushButton:hover {
    background-color: #d0d0d0;
}

/* 按下状态 */
QPushButton:pressed {
    background-color: #c0c0c0;
}

/* 选中状态 */
QCheckBox:checked {
    color: green;
}

/* 禁用状态 */
QPushButton:disabled {
    background-color: #a0a0a0;
    color: #707070;
}

/* 焦点状态 */
QLineEdit:focus {
    border: 2px solid #0078d7;
}

8.5 自定义应用主题

创建完整的应用主题:

class ThemeManager:
    @staticmethod
    def apply_dark_theme(app):
        app.setStyleSheet("""
            /* 全局样式 */
            QWidget {
                background-color: #2b2b2b;
                color: #e0e0e0;
                font-family: "Segoe UI", Arial, sans-serif;
            }
            
            /* 主窗口 */
            QMainWindow {
                background-color: #1e1e1e;
            }
            
            /* 菜单 */
            QMenuBar {
                background-color: #2b2b2b;
            }
            
            QMenuBar::item {
                background-color: transparent;
                padding: 6px 10px;
            }
            
            QMenuBar::item:selected {
                background-color: #3a3a3a;
            }
            
            QMenu {
                background-color: #2b2b2b;
                border: 1px solid #555555;
            }
            
            QMenu::item {
                padding: 6px 20px;
            }
            
            QMenu::item:selected {
                background-color: #3a3a3a;
            }
            
            /* 按钮 */
            QPushButton {
                background-color: #0078d7;
                color: white;
                border: none;
                padding: 8px 16px;
                border-radius: 4px;
            }
            
            QPushButton:hover {
                background-color: #0086f0;
            }
            
            QPushButton:pressed {
                background-color: #006ac1;
            }
            
            QPushButton:disabled {
                background-color: #555555;
                color: #888888;
            }
            
            /* 输入框 */
            QLineEdit, QTextEdit, QPlainTextEdit {
                background-color: #333333;
                border: 1px solid #555555;
                border-radius: 3px;
                padding: 4px;
                color: #e0e0e0;
            }
            
            QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus {
                border: 1px solid #0078d7;
            }
            
            /* 下拉框 */
            QComboBox {
                background-color: #333333;
                border: 1px solid #555555;
                border-radius: 3px;
                padding: 4px 8px;
                color: #e0e0e0;
            }
            
            QComboBox::drop-down {
                subcontrol-origin: padding;
                subcontrol-position: top right;
                width: 20px;
                border-left: 1px solid #555555;
            }
            
            /* 复选框 */
            QCheckBox {
                spacing: 8px;
            }
            
            QCheckBox::indicator {
                width: 16px;
                height: 16px;
            }
            
            /* 选项卡 */
            QTabWidget::pane {
                border: 1px solid #555555;
            }
            
            QTabBar::tab {
                background-color: #2b2b2b;
                border: 1px solid #555555;
                padding: 6px 12px;
            }
            
            QTabBar::tab:selected {
                background-color: #333333;
            }
            
            /* 滚动条 */
            QScrollBar:vertical {
                background-color: #2b2b2b;
                width: 12px;
                margin: 12px 0 12px 0;
            }
            
            QScrollBar::handle:vertical {
                background-color: #505050;
                min-height: 20px;
                border-radius: 6px;
            }
            
            QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
                border: none;
                background: none;
            }
        """)
    
    @staticmethod
    def apply_light_theme(app):
        # 实现浅色主题...
        pass

# 使用示例
if __name__ == "__main__":
    app = QApplication([])
    ThemeManager.apply_dark_theme(app)
    
    # 创建应用主窗口和其他界面元素...
    # ...
    
    app.exec()

8.6 动态切换主题

实现主题切换功能:

class ThemeSwitchableApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Theme Switcher")
        self.setGeometry(100, 100, 800, 600)
        
        # 创建中央部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 创建布局
        layout = QVBoxLayout(central_widget)
        
        # 添加一些控件
        title = QLabel("Theme Switching Demo")
        title.setAlignment(Qt.AlignCenter)
        title.setFont(QFont("Arial", 18))
        
        self.text_edit = QTextEdit()
        self.text_edit.setPlaceholderText("Type something here...")
        
        self.combo = QComboBox()
        self.combo.addItems(["Option 1", "Option 2", "Option 3"])
        
        self.checkbox = QCheckBox("Enable feature")
        
        # 主题切换按钮
        theme_layout = QHBoxLayout()
        
        self.light_theme_btn = QPushButton("Light Theme")
        self.light_theme_btn.clicked.connect(self.apply_light_theme)
        
        self.dark_theme_btn = QPushButton("Dark Theme")
        self.dark_theme_btn.clicked.connect(self.apply_dark_theme)
        
        theme_layout.addWidget(self.light_theme_btn)
        theme_layout.addWidget(self.dark_theme_btn)
        
        # 添加控件到布局
        layout.addWidget(title)
        layout.addWidget(self.text_edit)
        layout.addWidget(self.combo)
        layout.addWidget(self.checkbox)
        layout.addLayout(theme_layout)
        
        # 设置初始主题
        self.current_theme = "light"
        self.apply_light_theme()
    
    def apply_light_theme(self):
        if self.current_theme == "light":
            return
            
        self.current_theme = "light"
        
        # 提供一个简单的浅色主题
        self.setStyleSheet("""
            QWidget {
                background-color: #f0f0f0;
                color: #333333;
            }
            
            QPushButton {
                background-color: #e0e0e0;
                border: 1px solid #d0d0d0;
                padding: 6px 12px;
                border-radius: 4px;
            }
            
            QPushButton:hover {
                background-color: #d0d0d0;
            }
            
            QTextEdit, QComboBox {
                background-color: white;
                border: 1px solid #d0d0d0;
                border-radius: 3px;
                padding: 4px;
            }
        """)
    
    def apply_dark_theme(self):
        if self.current_theme == "dark":
            return
            
        self.current_theme = "dark"
        
        # 提供一个简单的深色主题
        self.setStyleSheet("""
            QWidget {
                background-color: #2b2b2b;
                color: #e0e0e0;
            }
            
            QPushButton {
                background-color: #0078d7;
                color: white;
                border: none;
                padding: 6px 12px;
                border-radius: 4px;
            }
            
            QPushButton:hover {
                background-color: #0086f0;
            }
            
            QTextEdit, QComboBox {
                background-color: #333333;
                border: 1px solid #555555;
                border-radius: 3px;
                padding: 4px;
                color: #e0e0e0;
            }
        """)

9. Qt Designer 可视化设计

Qt Designer是一个强大的可视化界面设计工具,与PySide6集成使用可以大大提高开发效率。

9.1 Qt Designer 基础

Qt Designer随PySide6一起安装,可以通过命令行启动:

pyside6-designer

主要功能区域:

  1. 控件面板:可用的控件和布局
  2. 对象检查器:显示控件层次结构
  3. 属性编辑器:修改选中控件的属性
  4. 信号/槽编辑器:可视化连接信号和槽
  5. 资源浏览器:管理图像等资源
  6. 动作编辑器:创建和管理应用动作

9.2 创建界面

在Qt Designer中设计界面的基本步骤:

  1. 选择模板(如Widget、Main Window、Dialog)
  2. 从控件面板拖放控件到设计区域
  3. 设置布局(右键区域选择布局类型)
  4. 调整控件属性(在属性编辑器中)
  5. 设置控件的对象名(objectName属性)
  6. 保存为.ui文件

9.3 加载.ui文件

9.3.1 使用QUiLoader

动态加载.ui文件:

from PySide6.QtWidgets import QApplication
from PySide6.QtUiTools import QUiLoader
from PySide6.QtCore import QFile, QIODevice

app = QApplication([])

# 加载UI文件
ui_file = QFile("mainwindow.ui")
ui_file.open(QIODevice.ReadOnly)

loader = QUiLoader()
window = loader.load(ui_file)
ui_file.close()

# 访问UI中的控件
window.pushButton.clicked.connect(lambda: print("Button clicked!"))
window.lineEdit.textChanged.connect(lambda text: print(f"Text: {text}"))

window.show()
app.exec()
9.3.2 使用uic编译

将.ui文件转换为Python代码:

pyside6-uic mainwindow.ui -o ui_mainwindow.py

然后在代码中使用:

from PySide6.QtWidgets import QApplication, QMainWindow
from ui_mainwindow import Ui_MainWindow

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        
        # 设置UI
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        
        # 连接信号
        self.ui.pushButton.clicked.connect(self.on_button_clicked)
        self.ui.lineEdit.textChanged.connect(self.on_text_changed)
    
    def on_button_clicked(self):
        print("Button clicked!")
    
    def on_text_changed(self, text):
        print(f"Text: {text}")

app = QApplication([])
window = MainWindow()
window.show()
app.exec()

9.4 多界面管理

管理多个界面文件:

class Application:
    def __init__(self):
        self.app = QApplication([])
        self.main_window = self.load_ui("mainwindow.ui")
        self.dialog = None
    
    def load_ui(self, ui_file_name):
        ui_file = QFile(ui_file_name)
        ui_file.open(QIODevice.ReadOnly)
        loader = QUiLoader()
        window = loader.load(ui_file)
        ui_file.close()
        return window
    
    def show_dialog(self):
        # 懒加载对话框
        if not self.dialog:
            self.dialog = self.load_ui("dialog.ui")
            # 连接对话框信号
            self.dialog.accepted.connect(self.on_dialog_accepted)
        
        self.dialog.show()
    
    def on_dialog_accepted(self):
        print("Dialog accepted")
    
    def run(self):
        # 连接主窗口信号
        self.main_window.actionShowDialog.triggered.connect(self.show_dialog)
        
        self.main_window.show()
        return self.app.exec()

if __name__ == "__main__":
    app = Application()
    sys.exit(app.run())

9.5 表单继承

继承Designer创建的UI类:

from PySide6.QtWidgets import QApplication, QMainWindow
from ui_mainwindow import Ui_MainWindow

# 继承UI类
class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        
        # 设置UI
        self.setupUi(self)
        
        # 添加信号连接
        self.pushButton.clicked.connect(self.on_button_clicked)
        self.actionExit.triggered.connect(self.close)
    
    def on_button_clicked(self):
        self.statusBar().showMessage("Button clicked!")
        self.label.setText(f"Hello, {self.lineEdit.text()}")

app = QApplication([])
window = MainWindow()
window.show()
app.exec()

9.6 资源文件管理

9.6.1 创建资源文件

创建资源文件(resources.qrc):

DOCTYPE RCC>
<RCC version="1.0">
    <qresource prefix="/images">
        <file>icons/new.pngfile>
        <file>icons/open.pngfile>
        <file>icons/save.pngfile>
    qresource>
    <qresource prefix="/styles">
        <file>styles/dark.qssfile>
        <file>styles/light.qssfile>
    qresource>
RCC>
9.6.2 编译资源文件
pyside6-rcc resources.qrc -o resources_rc.py
9.6.3 使用资源

在.ui文件中引用资源:

import resources_rc

在代码中使用资源:

from PySide6.QtGui import QIcon
from PySide6.QtCore import QFile, QTextStream

# 使用图标
icon = QIcon(":/images/icons/save.png")
button.setIcon(icon)

# 加载样式表
style_file = QFile(":/styles/styles/dark.qss")
style_file.open(QFile.ReadOnly | QFile.Text)
stream = QTextStream(style_file)
style_sheet = stream.readAll()
app.setStyleSheet(style_sheet)

10. 多线程与并发处理

在GUI应用程序中,长时间运行的任务应该在单独的线程中执行,以避免界面冻结。

10.1 QThread 基本用法

使用QThread创建独立线程:

from PySide6.QtCore import QThread, Signal
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QProgressBar, QVBoxLayout, QWidget
import time

class Worker(QThread):
    # 定义信号
    progress = Signal(int)
    completed = Signal()
    
    def run(self):
        # 线程的主要执行代码
        for i in range(101):
            # 执行耗时操作
            time.sleep(0.1)  # 模拟耗时操作
            
            # 发出进度信号
            self.progress.emit(i)
        
        # 发出完成信号
        self.completed.emit()

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QThread Example")
        self.resize(400, 200)
        
        # 创建中央部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 创建布局
        layout = QVBoxLayout(central_widget)
        
        # 创建进度条
        self.progress_bar = QProgressBar()
        layout.addWidget(self.progress_bar)
        
        # 创建按钮
        self.start_button = QPushButton("Start Task")
        self.start_button.clicked.connect(self.start_task)
        layout.addWidget(self.start_button)
        
        # 创建工作线程
        self.worker = Worker()
        self.worker.progress.connect(self.update_progress)
        self.worker.completed.connect(self.task_completed)
    
    def start_task(self):
        self.start_button.setEnabled(False)
        self.progress_bar.setValue(0)
        self.worker.start()
    
    def update_progress(self, value):
        self.progress_bar.setValue(value)
    
    def task_completed(self):
        self.start_button.setEnabled(True)
        self.start_button.setText("Start Again")

app = QApplication([])
window = MainWindow()
window.show()
app.exec()

10.2 QRunnable 和线程池

使用QThreadPool管理多个并发任务:

from PySide6.QtCore import QRunnable, QThreadPool, QObject, Signal, Slot

# 信号需要QObject
class WorkerSignals(QObject):
    progress = Signal(int)
    completed = Signal()
    error = Signal(str)

class Task(QRunnable):
    def __init__(self, fn, *args, **kwargs):
        super().__init__()
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()
    
    @Slot()
    def run(self):
        try:
            # 执行函数
            result = self.fn(*self.args, **self.kwargs)
        except Exception as e:
            self.signals.error.emit(str(e))
        else:
            self.signals.completed.emit()

# 使用线程池
def start_tasks():
    # 获取全局线程池
    pool = QThreadPool.globalInstance()
    print(f"使用最大 {pool.maxThreadCount()} 个线程")
    
    # 创建和提交任务
    for i in range(5):
        task = Task(long_running_function, i)
        pool.start(task)

11. 数据持久化

PySide6应用通常需要保存用户设置、缓存或其他数据。

11.1 QSettings

用于存储应用程序设置:

from PySide6.QtCore import QSettings

# 创建设置对象
settings = QSettings("MyCompany", "MyApp")

# 保存设置
settings.setValue("window/size", window.size())
settings.setValue("window/position", window.pos())
settings.setValue("user/name", "John Doe")
settings.setValue("user/preferences", {"theme": "dark", "language": "en"})

# 读取设置
window_size = settings.value("window/size")
user_name = settings.value("user/name", "Guest")  # 提供默认值
user_prefs = settings.value("user/preferences", {})

# 检查键是否存在
if settings.contains("user/name"):
    print("User name is set")

# 删除设置
settings.remove("user/name")

# 清除所有设置
settings.clear()

11.2 SQLite数据库集成

使用内置sqlite3模块或QSqlDatabase:

from PySide6.QtSql import QSqlDatabase, QSqlQuery

# 创建连接
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName("myapp.db")

if not db.open():
    print("Cannot open database")
    print(db.lastError().text())
    return

# 创建表
query = QSqlQuery()
query.exec_("""
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE
    )
""")

# 插入数据
query.prepare("INSERT INTO users (name, email) VALUES (:name, :email)")
query.bindValue(":name", "John Doe")
query.bindValue(":email", "[email protected]")
query.exec_()

# 查询数据
query.exec_("SELECT * FROM users")
while query.next():
    user_id = query.value(0)
    name = query.value(1)
    email = query.value(2)
    print(f"User: {user_id}, {name}, {email}")

# 关闭数据库
db.close()

12. 高级图形与动画

PySide6提供了丰富的图形和动画功能。

12.1 自定义绘图

使用QPainter进行自定义绘图:

from PySide6.QtWidgets import QWidget
from PySide6.QtGui import QPainter, QPen, QBrush
from PySide6.QtCore import Qt

class DrawingWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.setMinimumSize(300, 200)
    
    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        
        # 设置画笔(轮廓)
        pen = QPen(Qt.blue, 2)
        painter.setPen(pen)
        
        # 设置画刷(填充)
        brush = QBrush(Qt.green)
        painter.setBrush(brush)
        
        # 绘制矩形
        painter.drawRect(50, 50, 100, 50)
        
        # 绘制圆形
        painter.setPen(QPen(Qt.red, 3))
        painter.setBrush(QBrush(Qt.yellow))
        painter.drawEllipse(200, 50, 80, 80)
        
        # 绘制文本
        painter.setPen(Qt.black)
        painter.drawText(50, 150, "Hello, PySide6!")

12.2 动画效果

使用QPropertyAnimation创建平滑动画:

from PySide6.QtCore import QPropertyAnimation, QEasingCurve, Property

class AnimatedButton(QPushButton):
    def __init__(self, text):
        super().__init__(text)
        self._opacity = 1.0
    
    def opacity(self):
        return self._opacity
    
    def setOpacity(self, opacity):
        self._opacity = opacity
        self.update()
    
    # 创建属性
    opacity = Property(float, opacity, setOpacity)
    
    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setOpacity(self._opacity)
        super().paintEvent(event)

# 创建动画
button = AnimatedButton("Animated Button")
animation = QPropertyAnimation(button, b"opacity")
animation.setDuration(1000)  # 1秒
animation.setStartValue(1.0)
animation.setEndValue(0.2)
animation.setEasingCurve(QEasingCurve.InOutQuad)
animation.start()

13. 国际化与本地化

使PySide6应用支持多语言。

13.1 使用QTranslator

from PySide6.QtCore import QTranslator, QLocale

# 创建翻译器
translator = QTranslator()

# 加载翻译文件(根据系统语言)
locale = QLocale.system().name()  # 如 "zh_CN"
if translator.load(f"myapp_{locale}", "translations"):
    app.installTranslator(translator)

13.2 使用tr()进行文本标记

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        
        self.setWindowTitle(self.tr("My Application"))
        
        label = QLabel(self.tr("Hello, World!"))
        button = QPushButton(self.tr("Click Me"))
        
        # 带复数和上下文的翻译
        items_count = 5
        message = self.tr("%n item(s) selected", "", items_count)
        
        # 带上下文的翻译
        context_message = self.tr("Open", "File menu action")

14. 实战项目开发

14.1 应用架构设计

使用模型-视图-控制器(MVC)或模型-视图-视图模型(MVVM)架构:

# 模型 - 处理数据
class TodoModel:
    def __init__(self):
        self.items = []
    
    def add_item(self, text, due_date=None):
        self.items.append({"text": text, "completed": False, "due_date": due_date})
    
    def complete_item(self, index):
        if 0 <= index < len(self.items):
            self.items[index]["completed"] = True

# 视图模型 - 连接模型和视图
class TodoViewModel(QObject):
    item_added = Signal(str, object)
    item_completed = Signal(int)
    
    def __init__(self, model):
        super().__init__()
        self.model = model
    
    def add_item(self, text, due_date=None):
        self.model.add_item(text, due_date)
        self.item_added.emit(text, due_date)
    
    def complete_item(self, index):
        self.model.complete_item(index)
        self.item_completed.emit(index)

# 视图 - 用户界面
class TodoView(QMainWindow):
    def __init__(self, view_model):
        super().__init__()
        self.view_model = view_model
        self.setupUi()
        
        # 连接视图模型的信号
        self.view_model.item_added.connect(self.add_item_to_ui)
        self.view_model.item_completed.connect(self.mark_item_completed)

14.2 项目质量保证

  • 使用类型提示
  • 添加单元测试
  • 使用版本控制
  • 编写文档
  • 进行代码审查

15. 打包与发布

15.1 使用PyInstaller打包

将PySide6应用打包成可执行文件:

# 安装PyInstaller
pip install pyinstaller

# 打包应用
pyinstaller --name=MyApp --windowed --onefile main.py

# 指定图标和其他资源
pyinstaller --name=MyApp --windowed --onefile --icon=app_icon.ico main.py

15.2 使用cx_Freeze打包

另一个打包选项:

# setup.py
from cx_Freeze import setup, Executable

setup(
    name="MyApp",
    version="1.0",
    description="My PySide6 Application",
    executables=[Executable("main.py", target_name="MyApp.exe", icon="app_icon.ico")],
    options={
        "build_exe": {
            "packages": ["PySide6"],
            "include_files": ["icons/", "translations/"]
        }
    }
)

运行:

python setup.py build

16. 总结

本文全面介绍了PySide6桌面应用开发,从基础概念、控件使用、布局管理,到高级特性如多线程、动画和国际化。PySide6结合了Qt强大的功能和Python的简洁性,是创建专业级桌面应用的理想选择。

掌握PySide6可以让开发者创建跨平台、功能丰富的应用程序,从简单工具到复杂企业软件。通过持续学习和实践,你可以充分发挥PySide6的潜力,构建出美观、高效且用户友好的桌面应用。


作者:climber1121
链接:https://blog.csdn.net/climber1121
来源:CSDN
版权声明:本文为博主原创文章,转载请附上原文出处链接和本声明。

你可能感兴趣的:(Python基础,QT,python,开发语言)