在桌面應用開發中,“主題切換(深色 / 淺色模式)”已經不再是錦上添花的功能,而是逐漸成為一種用户剛需。尤其對於長時間使用的 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 標籤 -
QPushButton、QTableView→ 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的項目中的實踐過程
我們根據前面的介紹,在項目目錄中創建兩個不同主題樣式的文件,如下所示。
然後根據樣式的需要定義對應的相關內容,如下是淺色的主題定義,通過定義對應控件的顏色、字體、背景色等相關屬性,實現統一的效果。
通過輔助類,我們創建幾個菜單來實現不同樣式的切換。
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))
菜單界面效果如下所示。
其中樣式的信號處理,我們通過一個單件的樣式總線對象來處理。
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)
暗色主題的效果如下所示。
如果不喜歡厚重的主題,我們也可以切換會原來的默認主題。
以上就是我們在定義不同主題,實現主題切換的過程,我們可以根據需要,定義更多有特色的主題樣式,而具有統一效果的主題樣式,我們可以通過AI的詢問方式,獲得完整的樣式代碼,從而構建個性化的效果。