【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.png</file>
            <file>icons/open.png</file>
            <file>icons/save.png</file>
        </qresource>
        <qresource prefix="/styles">
            <file>styles/dark.qss</file>
            <file>styles/light.qss</file>
        </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", "john@example.com")
    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
    版权声明:本文为博主原创文章,转载请附上原文出处链接和本声明。

    作者:climber1121

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【Python桌面应用开发】PySide6界面设计全方位指南

    发表回复