博客 / 詳情

返回

在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的詢問方式,獲得完整的樣式代碼,從而構建個性化的效果。

 

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.