在PySide6/PyQt6的项目中实现样式切换处理

在桌面应用开发中,“主题切换(深色 / 浅色模式)”已经不再是锦上添花的功能,而是逐渐成为一种用户刚需。尤其对于长时间使用的 ERP 工具类应用来说,良好的视觉舒适度对用户体验影响巨大。本篇随笔针对PySide6/PyQt6的项目的实现案例,介绍如何实现样式切换处理。

Qt 官方本身提供了 QStyle 体系,但如果想做到:

  • 可自定义配色

  • 可作为品牌皮肤

  • 可热切换(无需重启)

  • 多主题共存

  • 可持久化

那么,采用 QSS (Qt Style Sheet) + 统一主题管理器的方案无疑是目前最成熟、最实用、性价比最高的选择。

本文将从 原理 → 架构设计 → 实现方式 → 最佳实践 四个维度,完整介绍在 PySide6 / PyQt6 项目中如何构建一套“企业级主题切换系统”。

一、为什么不能简单地用 setStyle

很多初学者在做样式适配时,都会走入一个误区:

btn.setStyleSheet("background:red")
table.setStyleSheet("color:white")
...

这种写法短期“能用”,长期会带来三个严重问题:

1. 样式分散,难维护

不同窗口、不同控件中的样式写法彼此独立,一个主题改动需要满项目搜索替换,很容易遗漏。


2. 逻辑和表现混杂

UI 逻辑代码被大量 CSS 淹没,维护成本剧增,违反基本的架构解耦原则。


3. 无法进行主题切换

没有分主题文件结构,一旦需要切换风格就必须“重写所有 setStyle”,几乎不可实现。


✅ 因此,如果项目规模超过 3 个界面,我们就必须放弃零散 setStyle 写法,转而使用 QSS 主题体系。

二、什么是 QSS?

Qt 中的 QSS (Qt Style Sheet) 本质和 CSS 非常类似,几乎可以视为 CSS 在 Qt 世界中的实现。

对 Qt 控件来说:

  • QWidget → HTML 标签

  • QPushButtonQTableView → CSS 选择器

  • background / border / padding / color → 样式属性

例如:

QPushButton {
    background: #3a3a3a;
    border-radius: 6px;
    color: white;
}

QSS 能做到:

  • 全局覆盖

  • 继承 & 层级作用

  • 精准控件控制

  • 即时加载刷新

我们希望做到:

业务代码永远不接触 CSS
主题由独立模块统一调度

project/
│
├── main.py
├── core/
│   └── theme_manager.py
└── themes/
    ├── light.qss
    └── dark.qss

通过 ThemeManager 将 UI 层与样式层完全隔离,可读性和可维护性大幅提升。

我们需要一个负责三件事的主题管理器统一类:

  • 读取主题文件

  • 调用 Qt API 应用主题

  • 记录并恢复用户选择

QSS 主题文件设计示例:以下是一个简化版本:

浅色主题

QWidget {
    background: #ffffff;
    color: #202020;
}

QPushButton {
    background: #e8e8e8;
    border-radius: 4px;
}

深色主题

QWidget {
    background: #2b2b2b;
    color: #f0f0f0;
}

QPushButton {
    background: #3a3a3a;
    color: #ffffff;
}

三、UI 中的使用方式

我们通过创建系统相关的菜单,如定义样式切换的菜单,然后绑定对应的菜单信号处理。

action_dark.triggered.connect(lambda: ThemeManager.apply("dark"))
action_light.triggered.connect(lambda: ThemeManager.apply("light"))

这样切换的时候,无需重启——Qt 会自动刷新全部控件。

通过样式管理器,实现不同主题演示的即时切换。

主题系统并不是简单的“换几个颜色”。

它是 UI 架构中不可忽视的一环:

  • 影响可维护性

  • 决定代码整洁度

  • 直接关系用户体验


在 PySide6 / PyQt6 中,

QSS + ThemeManager + 动态切换 + 持久化

这一套组合,几乎是目前最成熟可靠的主题实现方案,没有之一。

真正的统一UI的样式,包含 5 个维度:

维度说明
色板统一背景色、主色、强调色、警示色
字体统一字体家族、字号梯度
控件形态圆角、border、padding
交互动效hover、press、disable
布局密度表格行距、控件间距

 

四、具体在PySide6/PyQt6的项目中的实践过程

我们根据前面的介绍,在项目目录中创建两个不同主题样式的文件,如下所示。

image

然后根据样式的需要定义对应的相关内容,如下是浅色的主题定义,通过定义对应控件的颜色、字体、背景色等相关属性,实现统一的效果。

image

通过辅助类,我们创建几个菜单来实现不同样式的切换。

        theme_menu = menu_bar.addMenu("界面主题")
        light_action = ControlUtil.create_menu(
            self, theme_menu, "浅色主题", "info"
        )
        light_action.triggered.connect(lambda: self.set_theme(1))
        dark_action = ControlUtil.create_menu(
            self, theme_menu, "深色主题", "info"
        )
        dark_action.triggered.connect(lambda: self.set_theme(2))
        system_action = ControlUtil.create_menu(
            self, theme_menu, "初始主题", "info"
        )
        system_action.triggered.connect(lambda: self.set_theme(0))

菜单界面效果如下所示。

image

其中样式的信号处理,我们通过一个单件的样式总线对象来处理。

    def set_theme(self, theme_type):
        """设置主题颜色"""
        self.theme_type = theme_type
        # 切换主题颜色
        ThemeBus().set_theme_type(self.theme_type)

其中,我们对样式总线对象的变化进行信号绑定处理。

        #监听主题变化的事件
        ThemeBus().theme_type_changed.connect(self.on_theme_changed)

对主题样式的变化进行处理。

    def on_theme_changed(self, theme_type):
        """主题变化的事件处理"""
        
        theme_map = {
            1: "light",
            2: "dark",
            0: "" #不设置主题,默认使用系统主题
        } 
        theme_name = theme_map[self.theme_type]

        self.log.info(f"主题变化: {theme_type}:{theme_name}")
        # 切换主题颜色
        ThemedHelper.apply(theme_type)

这个主题的辅助类,主要就是根据当前总线的样式值,加载对应的样式文件进行设置,如果为空,这还原为最初的默认样式。

class ThemedHelper:
    """控件主题辅助类"""
 
    @staticmethod
    def apply(theme_type: int = 0):
        """应用主题样式"""
        theme_name =  ""
        if theme_type == 1:
            theme_name= "light"
        elif theme_type == 2:
            theme_name= "dark" 
        else:
            theme_name = ""
            
        if not theme_name:
            qss = "" # 自动主题, 无需加载 qss 文件
        else:
            with open(f"app/themes/{theme_name}.qss", encoding="utf8") as f:
                qss = f.read()
        
        app : QApplication = QApplication.instance()
        if app:
            app.setStyle("Fusion")
            app.setStyleSheet(qss)

暗色主题的效果如下所示。

image

 如果不喜欢厚重的主题,我们也可以切换会原来的默认主题。

image

以上就是我们在定义不同主题,实现主题切换的过程,我们可以根据需要,定义更多有特色的主题样式,而具有统一效果的主题样式,我们可以通过AI的询问方式,获得完整的样式代码,从而构建个性化的效果。

 

alt 专注于代码生成工具、.Net/Python 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架、Python开发框架等框架产品。
  转载请注明出处:撰写人:伍华聪  http://www.iqidi.com 
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值