版权所有:enkia
项目地址:https://github.com/enkia/tokyo-night-vscode-theme
diff --git a/requirements.txt b/requirements.txt
index dd0acbc..2863ba3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,6 @@
PySide6>=6.0.0
PySide6-Fluent-Widgets>=1.0.0
+PySideSix-Frameless-Window>=0.1.0
Pillow>=9.0.0
requests>=2.32.0
numpy>=1.21.0
diff --git a/ui/color_preview.py b/ui/color_preview.py
index b0b5b37..860e810 100644
--- a/ui/color_preview.py
+++ b/ui/color_preview.py
@@ -306,7 +306,7 @@ class ColorDotBar(QWidget):
}
# 打开编辑对话框(预览配色场景不显示名称输入)
- dialog = EditPaletteDialog(palette_data=palette_data, parent=self, show_name_input=False)
+ dialog = EditPaletteDialog(palette_data=palette_data, parent=self.window(), show_name_input=False)
if dialog.exec() == EditPaletteDialog.DialogCode.Accepted:
new_palette_data = dialog.get_palette_data()
if new_palette_data and 'colors' in new_palette_data:
diff --git a/utils/theme_colors.py b/utils/theme_colors.py
index 50d7137..cc77ce2 100644
--- a/utils/theme_colors.py
+++ b/utils/theme_colors.py
@@ -248,6 +248,21 @@ def get_dialog_bg_color():
return QColor(32, 32, 32) if isDarkTheme() else QColor(255, 255, 255)
+def get_close_button_hover_bg_color():
+ """获取关闭按钮悬停背景颜色"""
+ return QColor(196, 43, 28) if isDarkTheme() else QColor(232, 17, 35)
+
+
+def get_close_button_hover_color():
+ """获取关闭按钮悬停图标颜色"""
+ return QColor(255, 255, 255)
+
+
+def get_close_button_pressed_color():
+ """获取关闭按钮按下图标颜色"""
+ return QColor(255, 255, 255)
+
+
# ========== Zone框颜色 ==========
def get_zone_background_color():
"""获取Zone框背景颜色"""
diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md"
index 796a9a4..362593d 100644
--- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md"
+++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md"
@@ -697,3 +697,421 @@ def test_queued_connection_safety(qtbot):
| terminate() 崩溃 | 使用 cancel() + wait() 优雅退出 |
| 线程销毁警告 | 服务类添加 __del__ 析构函数 |
| 信号未断开崩溃 | closeEvent 中断开全局信号 |
+
+---
+
+## 10. PySideSix-Frameless-Window 无边框窗口改造经验
+
+### 10.1 改造背景与目标
+
+将传统 `QDialog` 改造为 `FramelessDialog`,实现 Fluent Design 风格的自定义标题栏,获得更现代、统一的界面效果。
+
+### 10.2 核心改造步骤
+
+#### 10.2.1 依赖安装
+
+```bash
+pip install PySideSix-Frameless-Window
+```
+
+在 `requirements.txt` 中添加:
+```
+PySideSix-Frameless-Window>=0.1.0
+```
+
+#### 10.2.2 导入变更
+
+```python
+# 原有导入
+from PySide6.QtWidgets import QDialog
+
+# 新增导入
+from qframelesswindow import FramelessDialog
+```
+
+#### 10.2.3 类继承变更
+
+```python
+# 从
+class EditPaletteDialog(QDialog):
+
+# 改为
+class EditPaletteDialog(FramelessDialog):
+```
+
+#### 10.2.4 初始化方法调整
+
+**移除的代码:**
+- `setWindowFlags()` 调用(FramelessDialog 已处理)
+- `set_window_title_bar_theme()` 相关代码(不再使用 Windows DWM API)
+- `showEvent` 中的标题栏主题更新逻辑
+
+**新增的代码:**
+```python
+def __init__(self, ...):
+ super().__init__(parent)
+
+ # 设置窗口标题
+ self.setWindowTitle("窗口标题")
+
+ # 设置自定义标题栏
+ self._setup_title_bar()
+
+ # 初始化样式(包含窗口背景色和标题颜色)
+ self._update_styles()
+
+ # 监听主题变化
+ self._theme_connection = qconfig.themeChangedFinished.connect(
+ self._update_styles
+ )
+```
+
+### 10.3 自定义标题栏实现
+
+#### 10.3.1 标题栏布局结构
+
+```
+[左边距(10px)] [Logo] [间距(8px)] [标题] [Stretch] [最小化] [最大化] [关闭]
+```
+
+#### 10.3.2 核心实现代码
+
+```python
+def _setup_title_bar(self):
+ """设置自定义 Fluent Design 风格标题栏"""
+ from PySide6.QtWidgets import QLabel
+ from PySide6.QtCore import Qt
+ from PySide6.QtGui import QPixmap
+ from utils.icon import get_icon_path
+ from utils.theme_colors import get_text_color
+
+ # 获取 FramelessDialog 内置的标题栏
+ title_bar = self.titleBar
+ h_layout = title_bar.layout()
+ if not h_layout:
+ return
+
+ # 获取主题颜色
+ text_color = get_text_color()
+ text_color_str = text_color.name()
+
+ # 创建标题栏控件
+ # 1. 左边距
+ left_spacer = QLabel(title_bar)
+ left_spacer.setFixedWidth(10)
+
+ # 2. Logo
+ icon_path = get_icon_path()
+ logo_label = None
+ if icon_path:
+ logo_label = QLabel(title_bar)
+ pixmap = QPixmap(icon_path)
+ if not pixmap.isNull():
+ logo_label.setPixmap(pixmap.scaled(
+ 20, 20,
+ Qt.AspectRatioMode.KeepAspectRatio,
+ Qt.TransformationMode.SmoothTransformation
+ ))
+
+ # 3. Logo和标题之间的间距
+ spacer_label = QLabel(title_bar)
+ spacer_label.setFixedWidth(8)
+
+ # 4. 标题
+ title_label = QLabel(self.windowTitle(), title_bar)
+ title_label.setStyleSheet(
+ f"color: {text_color_str}; font-size: 13px; font-weight: 500;"
+ )
+
+ # 按顺序插入到布局开头(倒序插入)
+ for widget in [title_label, spacer_label, logo_label, left_spacer]:
+ if widget:
+ h_layout.insertWidget(0, widget)
+ h_layout.setAlignment(widget, Qt.AlignmentFlag.AlignVCenter)
+
+ # 保存标题标签引用,以便主题切换时更新
+ self._title_label = title_label
+```
+
+### 10.4 主题适配关键经验
+
+#### 10.4.1 背景色设置方式
+
+**❌ 错误方式(样式表不生效):**
+```python
+self.setStyleSheet(f"FramelessDialog {{ background-color: {bg_color_str}; }}")
+```
+
+**✅ 正确方式(使用 QPalette):**
+```python
+from PySide6.QtGui import QPalette, QColor
+
+palette = self.palette()
+palette.setColor(QPalette.ColorRole.Window, QColor(bg_color_str))
+self.setPalette(palette)
+self.setAutoFillBackground(True)
+```
+
+#### 10.4.2 关闭按钮颜色更新
+
+```python
+def _update_close_button_color(self, text_color):
+ """更新关闭按钮颜色以适配主题"""
+ from utils.theme_colors import (
+ get_close_button_hover_bg_color,
+ get_close_button_hover_color,
+ get_close_button_pressed_color
+ )
+
+ title_bar = self.titleBar
+ if hasattr(title_bar, 'closeBtn') and title_bar.closeBtn:
+ close_btn = title_bar.closeBtn
+ # 设置正常状态颜色
+ close_btn.setNormalColor(text_color)
+ # 设置悬停状态颜色
+ close_btn.setHoverColor(get_close_button_hover_color())
+ close_btn.setHoverBackgroundColor(get_close_button_hover_bg_color())
+ # 设置按下状态颜色
+ close_btn.setPressedColor(get_close_button_pressed_color())
+```
+
+#### 10.4.3 完整样式更新方法
+
+```python
+def _update_styles(self):
+ """更新样式以适配主题"""
+ from PySide6.QtGui import QPalette, QColor
+ from utils.theme_colors import get_text_color, get_dialog_bg_color
+
+ text_color = get_text_color()
+ text_color_str = text_color.name()
+ bg_color = get_dialog_bg_color()
+
+ # 设置 QLabel 文字颜色样式表
+ self.setStyleSheet(f"""
+ QLabel {{
+ color: {text_color_str};
+ }}
+ """)
+
+ # 使用 QPalette 设置窗口背景色
+ palette = self.palette()
+ palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name()))
+ self.setPalette(palette)
+ self.setAutoFillBackground(True)
+
+ # 更新标题标签颜色
+ if hasattr(self, '_title_label') and self._title_label:
+ self._title_label.setStyleSheet(
+ f"color: {text_color_str}; font-size: 13px; font-weight: 500;"
+ )
+
+ # 更新关闭按钮颜色
+ self._update_close_button_color(text_color)
+```
+
+### 10.5 布局调整经验
+
+#### 10.5.1 为标题栏留出空间
+
+```python
+def setup_ui(self):
+ """设置界面布局"""
+ layout = QVBoxLayout(self)
+ # 顶部边距设置为40,为无边框窗口的标题栏留出空间
+ layout.setContentsMargins(20, 40, 20, 20)
+ layout.setSpacing(15)
+```
+
+#### 10.5.2 标题栏控件插入技巧
+
+**关键原则:倒序插入,保证正序显示**
+
+```python
+# 期望的最终顺序:左边距 → Logo → 间距 → 标题
+# 倒序插入:标题 → 间距 → Logo → 左边距
+for widget in [title_label, spacer_label, logo_label, left_spacer]:
+ if widget:
+ h_layout.insertWidget(0, widget)
+```
+
+### 10.6 常见问题与解决方案
+
+| 问题 | 原因 | 解决方案 |
+|------|------|----------|
+| 标题栏和内容重叠 | 未为标题栏留出空间 | 设置 `layout.setContentsMargins(20, 40, 20, 20)` |
+| 背景色不生效 | FramelessDialog 不使用样式表设置背景 | 使用 `QPalette` + `setAutoFillBackground(True)` |
+| 标题颜色不随主题切换 | 样式表未应用到标题栏控件 | 直接为标题标签设置样式表,并在 `_update_styles` 中更新 |
+| 关闭按钮颜色不随主题切换 | 未设置关闭按钮的颜色属性 | 使用 `closeBtn.setNormalColor()` 等方法设置 |
+| Logo/标题/按钮挤在一起 | 清除了原有布局的所有项 | 保留原有布局,只在开头插入新控件 |
+| 标题紧贴左边框 | 未设置左边距 | 添加固定宽度的占位符 QLabel |
+| 从子控件打开对话框时背景色异常 | 父窗口不是顶层窗口 | 使用 `parent=self.window()` 而不是 `parent=self` |
+
+### 10.7 主题颜色函数(theme_colors.py)
+
+在 `utils/theme_colors.py` 中添加关闭按钮颜色函数:
+
+```python
+def get_close_button_hover_bg_color():
+ """获取关闭按钮悬停背景颜色"""
+ return QColor(196, 43, 28) if isDarkTheme() else QColor(232, 17, 35)
+
+
+def get_close_button_hover_color():
+ """获取关闭按钮悬停图标颜色"""
+ return QColor(255, 255, 255)
+
+
+def get_close_button_pressed_color():
+ """获取关闭按钮按下图标颜色"""
+ return QColor(255, 255, 255)
+```
+
+### 10.8 改造检查清单
+
+- [ ] 安装 PySideSix-Frameless-Window 依赖
+- [ ] 修改类继承为 `FramelessDialog`
+- [ ] 移除 `setWindowFlags` 调用
+- [ ] 移除 `set_window_title_bar_theme` 相关代码
+- [ ] 移除 `_fix_taskbar_icon` 调用(FramelessDialog 自动处理)
+- [ ] 实现 `_setup_title_bar()` 方法
+- [ ] 实现 `_update_styles()` 方法
+- [ ] 实现 `_update_close_button_color()` 方法
+- [ ] 在 `theme_colors.py` 中添加关闭按钮颜色函数
+- [ ] 调整布局边距为标题栏留出空间
+- [ ] 监听主题变化信号
+- [ ] 在 `closeEvent` 中断开信号连接
+- [ ] 确保对话框父窗口是顶层窗口(`parent=self.window()`)
+- [ ] 测试深色/浅色主题切换
+- [ ] 测试窗口拖动和调整大小功能
+
+### 10.9 后续对话框改造模板
+
+```python
+from qframelesswindow import FramelessDialog
+from PySide6.QtGui import QPalette, QColor
+from utils.theme_colors import get_text_color, get_dialog_bg_color
+
+class CustomDialog(FramelessDialog):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setWindowTitle("对话框标题")
+ self.setFixedSize(400, 300)
+
+ # 设置自定义标题栏
+ self._setup_title_bar()
+
+ # 初始化样式
+ self._update_styles()
+
+ # 设置界面
+ self.setup_ui()
+
+ # 监听主题变化
+ self._theme_connection = qconfig.themeChangedFinished.connect(
+ self._update_styles
+ )
+
+ def _setup_title_bar(self):
+ """参考 10.3.2 实现"""
+ from PySide6.QtWidgets import QLabel
+ from PySide6.QtCore import Qt
+ from PySide6.QtGui import QPixmap
+ from utils.icon import get_icon_path
+ from utils.theme_colors import get_text_color
+
+ title_bar = self.titleBar
+ h_layout = title_bar.layout()
+ if not h_layout:
+ return
+
+ text_color = get_text_color()
+ text_color_str = text_color.name()
+
+ # 创建标题栏控件
+ left_spacer = QLabel(title_bar)
+ left_spacer.setFixedWidth(10)
+
+ icon_path = get_icon_path()
+ logo_label = None
+ if icon_path:
+ logo_label = QLabel(title_bar)
+ pixmap = QPixmap(icon_path)
+ if not pixmap.isNull():
+ logo_label.setPixmap(pixmap.scaled(
+ 20, 20,
+ Qt.AspectRatioMode.KeepAspectRatio,
+ Qt.TransformationMode.SmoothTransformation
+ ))
+
+ spacer_label = QLabel(title_bar)
+ spacer_label.setFixedWidth(8)
+
+ title_label = QLabel(self.windowTitle(), title_bar)
+ title_label.setStyleSheet(
+ f"color: {text_color_str}; font-size: 13px; font-weight: 500;"
+ )
+
+ for widget in [title_label, spacer_label, logo_label, left_spacer]:
+ if widget:
+ h_layout.insertWidget(0, widget)
+ h_layout.setAlignment(widget, Qt.AlignmentFlag.AlignVCenter)
+
+ self._title_label = title_label
+
+ def _update_close_button_color(self, text_color):
+ """参考 10.4.2 实现"""
+ from utils.theme_colors import (
+ get_close_button_hover_bg_color,
+ get_close_button_hover_color,
+ get_close_button_pressed_color
+ )
+
+ title_bar = self.titleBar
+ if hasattr(title_bar, 'closeBtn') and title_bar.closeBtn:
+ close_btn = title_bar.closeBtn
+ close_btn.setNormalColor(text_color)
+ close_btn.setHoverColor(get_close_button_hover_color())
+ close_btn.setHoverBackgroundColor(get_close_button_hover_bg_color())
+ close_btn.setPressedColor(get_close_button_pressed_color())
+
+ def _update_styles(self):
+ """参考 10.4.3 实现"""
+ text_color = get_text_color()
+ text_color_str = text_color.name()
+ bg_color = get_dialog_bg_color()
+
+ self.setStyleSheet(f"""
+ QLabel {{
+ color: {text_color_str};
+ }}
+ """)
+
+ palette = self.palette()
+ palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name()))
+ self.setPalette(palette)
+ self.setAutoFillBackground(True)
+
+ if hasattr(self, '_title_label') and self._title_label:
+ self._title_label.setStyleSheet(
+ f"color: {text_color_str}; font-size: 13px; font-weight: 500;"
+ )
+
+ self._update_close_button_color(text_color)
+
+ def setup_ui(self):
+ """设置界面"""
+ layout = QVBoxLayout(self)
+ layout.setContentsMargins(20, 40, 20, 20) # 注意顶部边距
+ # ... 其他控件
+
+ def closeEvent(self, event):
+ """关闭事件:断开信号连接"""
+ if hasattr(self, '_theme_connection'):
+ try:
+ qconfig.themeChangedFinished.disconnect(self._theme_connection)
+ except (TypeError, RuntimeError):
+ pass
+ delattr(self, '_theme_connection')
+ super().closeEvent(event)
+```
--
Gitee
From 48a06d1ee6f602ecc15137f418eacc4faf0cc146 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?=
Date: Mon, 16 Mar 2026 21:16:15 +0800
Subject: [PATCH 02/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E6=8F=90=E5=8F=96?=
=?UTF-8?q?=20BaseFramelessDialog=20=E5=9F=BA=E7=B1=BB=E5=B9=B6=E6=9B=B4?=
=?UTF-8?q?=E6=96=B0=E7=9B=B8=E5=85=B3=E6=96=87=E6=A1=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增 dialogs/base_frameless_dialog.py,封装无边框对话框通用功能
- 修改 dialogs/edit_palette.py,ColorPickerDialog 和 EditPaletteDialog 继承 BaseFramelessDialog
- 修改 dialogs/__init__.py,导出 BaseFramelessDialog
- 修改 ui/gradient_extract.py,修复 ColorPickerDialog 父窗口为顶层窗口
- 更新开发规范,新增 5.1.1 节无边框对话框基类使用规范
- 更新开发规范项目结构(1.3节),添加 base_frameless_dialog.py
- 更新开发规范 22.2 更新记录,添加版本 3.42
- 更新开发经验总结,新增 10.1.1 节推荐使用 BaseFramelessDialog 基类
- 更新开发经验总结 10.8 改造检查清单,区分新对话框和旧对话框迁移
- 更新开发经验总结 10.9 改造模板,提供简化版基类使用模板
---
dialogs/__init__.py | 2 +
dialogs/base_frameless_dialog.py | 141 +++++++++++++
dialogs/edit_palette.py | 154 ++------------
ui/gradient_extract.py | 3 +-
...17\351\252\214\346\200\273\347\273\223.md" | 195 +++++++++---------
...00\345\217\221\350\247\204\350\214\203.md" | 99 +++++++++
6 files changed, 356 insertions(+), 238 deletions(-)
create mode 100644 dialogs/base_frameless_dialog.py
diff --git a/dialogs/__init__.py b/dialogs/__init__.py
index a0bf7e5..5ab8219 100644
--- a/dialogs/__init__.py
+++ b/dialogs/__init__.py
@@ -1,6 +1,7 @@
"""对话框模块"""
from .about_dialog import AboutDialog
+from .base_frameless_dialog import BaseFramelessDialog
from .colorblind_dialog import ColorblindPreviewDialog
from .contrast_dialog import ContrastCheckDialog
from .edit_palette import EditPaletteDialog, ColorPickerDialog
@@ -9,6 +10,7 @@ from .update_dialog import UpdateAvailableDialog
__all__ = [
'AboutDialog',
+ 'BaseFramelessDialog',
'ColorblindPreviewDialog',
'ColorPickerDialog',
'ContrastCheckDialog',
diff --git a/dialogs/base_frameless_dialog.py b/dialogs/base_frameless_dialog.py
new file mode 100644
index 0000000..ad35f10
--- /dev/null
+++ b/dialogs/base_frameless_dialog.py
@@ -0,0 +1,141 @@
+# 第三方库导入
+from PySide6.QtWidgets import QLabel
+from PySide6.QtCore import Qt
+from PySide6.QtGui import QPixmap, QPalette, QColor
+from qframelesswindow import FramelessDialog
+
+# 项目模块导入
+from utils.icon import get_icon_path
+from utils.theme_colors import (
+ get_text_color, get_dialog_bg_color,
+ get_close_button_hover_bg_color,
+ get_close_button_hover_color,
+ get_close_button_pressed_color
+)
+
+
+class BaseFramelessDialog(FramelessDialog):
+ """无边框对话框基类
+
+ 提供统一的 Fluent Design 风格标题栏和主题适配功能。
+ 子类只需调用 _setup_title_bar() 和 _update_styles() 即可。
+
+ 使用示例:
+ class MyDialog(BaseFramelessDialog):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setWindowTitle("我的对话框")
+ self.setFixedSize(400, 300)
+
+ # 设置自定义标题栏
+ self._setup_title_bar()
+
+ # 初始化样式
+ self._update_styles()
+
+ # 设置界面
+ self.setup_ui()
+
+ # 监听主题变化
+ self._theme_connection = qconfig.themeChangedFinished.connect(
+ self._update_styles
+ )
+
+ def closeEvent(self, event):
+ # 断开信号连接
+ try:
+ qconfig.themeChangedFinished.disconnect(self._theme_connection)
+ except (TypeError, RuntimeError):
+ pass
+ super().closeEvent(event)
+ """
+
+ def _setup_title_bar(self):
+ """设置自定义 Fluent Design 风格标题栏"""
+ # 获取 FramelessDialog 内置的标题栏
+ title_bar = self.titleBar
+ h_layout = title_bar.layout()
+ if not h_layout:
+ return
+
+ # 获取主题颜色
+ text_color = get_text_color()
+ text_color_str = text_color.name()
+
+ # 创建标题栏控件:左边距、Logo、间距、标题
+ # 1. 左边距
+ left_spacer = QLabel(title_bar)
+ left_spacer.setFixedWidth(10)
+
+ # 2. Logo
+ icon_path = get_icon_path()
+ logo_label = None
+ if icon_path:
+ logo_label = QLabel(title_bar)
+ pixmap = QPixmap(icon_path)
+ if not pixmap.isNull():
+ logo_label.setPixmap(pixmap.scaled(
+ 20, 20,
+ Qt.AspectRatioMode.KeepAspectRatio,
+ Qt.TransformationMode.SmoothTransformation
+ ))
+
+ # 3. Logo和标题之间的间距
+ spacer_label = QLabel(title_bar)
+ spacer_label.setFixedWidth(8)
+
+ # 4. 标题
+ title_label = QLabel(self.windowTitle(), title_bar)
+ title_label.setStyleSheet(f"color: {text_color_str}; font-size: 13px; font-weight: 500;")
+
+ # 按顺序插入到布局开头(倒序插入)
+ for widget in [title_label, spacer_label, logo_label, left_spacer]:
+ if widget:
+ h_layout.insertWidget(0, widget)
+ h_layout.setAlignment(widget, Qt.AlignmentFlag.AlignVCenter)
+
+ # 保存标题标签引用,以便主题切换时更新
+ self._title_label = title_label
+
+ def _update_close_button_color(self, text_color):
+ """更新关闭按钮颜色以适配主题
+
+ Args:
+ text_color: 文本颜色 (QColor)
+ """
+ title_bar = self.titleBar
+ if hasattr(title_bar, 'closeBtn') and title_bar.closeBtn:
+ close_btn = title_bar.closeBtn
+ # 设置正常状态颜色
+ close_btn.setNormalColor(text_color)
+ # 设置悬停状态颜色
+ close_btn.setHoverColor(get_close_button_hover_color())
+ close_btn.setHoverBackgroundColor(get_close_button_hover_bg_color())
+ # 设置按下状态颜色
+ close_btn.setPressedColor(get_close_button_pressed_color())
+
+ def _update_styles(self):
+ """更新样式以适配主题"""
+ text_color = get_text_color()
+ text_color_str = text_color.name()
+ bg_color = get_dialog_bg_color()
+
+ # 设置 QLabel 文字颜色样式表
+ self.setStyleSheet(f"""
+ QLabel {{
+ color: {text_color_str};
+ }}
+ """)
+
+ # 使用 QPalette 设置窗口背景色
+ palette = self.palette()
+ palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name()))
+ self.setPalette(palette)
+ self.setAutoFillBackground(True)
+
+ # 更新标题标签颜色(如果存在)
+ if hasattr(self, '_title_label') and self._title_label:
+ self._title_label.setStyleSheet(f"color: {text_color_str}; font-size: 13px; font-weight: 500;")
+
+ # 更新关闭按钮颜色
+ self._update_close_button_color(text_color)
diff --git a/dialogs/edit_palette.py b/dialogs/edit_palette.py
index 3a040aa..68cf950 100644
--- a/dialogs/edit_palette.py
+++ b/dialogs/edit_palette.py
@@ -11,10 +11,8 @@ from PySide6.QtWidgets import (
from PySide6.QtGui import QColor, QPainter, QLinearGradient, QBrush, QPen, QMouseEvent
from qfluentwidgets import (
LineEdit, PrimaryPushButton, PushButton, ToolButton, FluentIcon,
- ScrollArea, isDarkTheme, qconfig
+ ScrollArea, qconfig
)
-from qframelesswindow import FramelessDialog
-
# 项目模块导入
from core import (
hex_to_rgb, rgb_to_hex, rgb_to_hsb, hsb_to_rgb,
@@ -22,9 +20,12 @@ from core import (
rgb_to_cmyk, cmyk_to_rgb, get_color_info
)
from core.config import get_config_manager
-from utils import tr, fix_windows_taskbar_icon_for_window, load_icon_universal
+from utils import tr, load_icon_universal
from utils.theme_colors import get_dialog_bg_color, get_text_color, get_border_color
+# 对话框模块导入
+from .base_frameless_dialog import BaseFramelessDialog
+
# ==================== 颜色选择器对话框组件 ====================
@@ -389,7 +390,7 @@ class ColorModeSliders(QWidget):
self._sliders[3].set_gradient(gradient_k)
-class ColorPickerDialog(QDialog):
+class ColorPickerDialog(BaseFramelessDialog):
"""颜色选择器对话框"""
def __init__(self, initial_color: Optional[Tuple[int, int, int]] = None, parent=None):
@@ -402,27 +403,18 @@ class ColorPickerDialog(QDialog):
self.setWindowTitle(tr('dialogs.color_picker.title'))
self.setFixedSize(520, 420)
- # 设置窗口标志
- self.setWindowFlags(
- Qt.WindowType.Window |
- Qt.WindowType.WindowTitleHint |
- Qt.WindowType.WindowCloseButtonHint |
- Qt.WindowType.CustomizeWindowHint
- )
+ # 设置自定义标题栏
+ self._setup_title_bar()
- # 设置背景色
- bg_color = get_dialog_bg_color()
- self.setStyleSheet(f"QDialog {{ background-color: {bg_color.name()}; }}")
+ # 初始化样式(包含窗口背景色和标题颜色)
+ self._update_styles()
self.setup_ui()
self._update_from_rgb()
- # 修复任务栏图标
- QTimer.singleShot(100, lambda: self._fix_taskbar_icon())
-
# 监听主题变化
self._theme_connection = qconfig.themeChangedFinished.connect(
- self._update_title_bar_theme
+ self._update_styles
)
def closeEvent(self, event):
@@ -436,7 +428,8 @@ class ColorPickerDialog(QDialog):
def setup_ui(self):
"""设置界面布局"""
main_layout = QVBoxLayout(self)
- main_layout.setContentsMargins(20, 20, 20, 20)
+ # 顶部边距设置为40,为无边框窗口的标题栏留出空间
+ main_layout.setContentsMargins(20, 40, 20, 20)
main_layout.setSpacing(15)
# 内容区域(左右分割)
@@ -736,22 +729,7 @@ class ColorPickerDialog(QDialog):
"""获取选择的颜色信息"""
return self._color_info
- def _update_title_bar_theme(self):
- """更新标题栏主题"""
- set_window_title_bar_theme(self, isDarkTheme())
- def _fix_taskbar_icon(self):
- """修复任务栏图标"""
- try:
- if self and self.isVisible():
- fix_windows_taskbar_icon_for_window(self)
- except RuntimeError:
- pass
-
- def showEvent(self, event):
- """窗口显示事件"""
- self._update_title_bar_theme()
- super().showEvent(event)
class ColorInputRow(QWidget):
@@ -839,7 +817,8 @@ class ColorInputRow(QWidget):
else:
initial = (128, 128, 128) # 默认灰色
- dialog = ColorPickerDialog(initial, self)
+ # 使用顶层窗口作为父窗口,避免背景色异常和两套窗口控制器
+ dialog = ColorPickerDialog(initial, self.window())
if dialog.exec() == QDialog.DialogCode.Accepted:
color_info = dialog.get_color_info()
if color_info:
@@ -949,7 +928,7 @@ class ColorInputRow(QWidget):
self._update_preview_style(color_info)
-class EditPaletteDialog(FramelessDialog):
+class EditPaletteDialog(BaseFramelessDialog):
"""编辑配色对话框"""
def __init__(self, default_name="", palette_data=None, parent=None, show_name_input=True):
@@ -1000,107 +979,6 @@ class EditPaletteDialog(FramelessDialog):
pass
super().closeEvent(event)
- def _setup_title_bar(self):
- """设置自定义 Fluent Design 风格标题栏"""
- from PySide6.QtWidgets import QLabel
- from PySide6.QtCore import Qt
- from PySide6.QtGui import QPixmap
- from utils.icon import get_icon_path
- from utils.theme_colors import get_text_color
-
- # 获取 FramelessDialog 内置的标题栏
- title_bar = self.titleBar
- h_layout = title_bar.layout()
- if not h_layout:
- return
-
- # 获取主题颜色
- text_color = get_text_color()
- text_color_str = text_color.name()
-
- # 创建标题栏控件:左边距、Logo、间距、标题
- # 1. 左边距
- left_spacer = QLabel(title_bar)
- left_spacer.setFixedWidth(10)
-
- # 2. Logo
- icon_path = get_icon_path()
- logo_label = None
- if icon_path:
- logo_label = QLabel(title_bar)
- pixmap = QPixmap(icon_path)
- if not pixmap.isNull():
- logo_label.setPixmap(pixmap.scaled(20, 20, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
-
- # 3. Logo和标题之间的间距
- spacer_label = QLabel(title_bar)
- spacer_label.setFixedWidth(8)
-
- # 4. 标题
- title_label = QLabel(self.windowTitle(), title_bar)
- title_label.setStyleSheet(f"color: {text_color_str}; font-size: 13px; font-weight: 500;")
-
- # 按顺序插入到布局开头(倒序插入)
- for widget in [title_label, spacer_label, logo_label, left_spacer]:
- if widget:
- h_layout.insertWidget(0, widget)
- h_layout.setAlignment(widget, Qt.AlignmentFlag.AlignVCenter)
-
- # 保存标题标签引用,以便主题切换时更新
- self._title_label = title_label
-
- def _update_close_button_color(self, text_color):
- """更新关闭按钮颜色以适配主题
-
- Args:
- text_color: 文本颜色 (QColor)
- """
- from utils.theme_colors import (
- get_close_button_hover_bg_color,
- get_close_button_hover_color,
- get_close_button_pressed_color
- )
-
- title_bar = self.titleBar
- if hasattr(title_bar, 'closeBtn') and title_bar.closeBtn:
- close_btn = title_bar.closeBtn
- # 设置正常状态颜色
- close_btn.setNormalColor(text_color)
- # 设置悬停状态颜色
- close_btn.setHoverColor(get_close_button_hover_color())
- close_btn.setHoverBackgroundColor(get_close_button_hover_bg_color())
- # 设置按下状态颜色
- close_btn.setPressedColor(get_close_button_pressed_color())
-
- def _update_styles(self):
- """更新样式以适配主题"""
- from PySide6.QtGui import QPalette, QColor
- from utils.theme_colors import get_text_color, get_dialog_bg_color
-
- text_color = get_text_color()
- text_color_str = text_color.name()
- bg_color = get_dialog_bg_color()
-
- # 设置 QLabel 文字颜色样式表
- self.setStyleSheet(f"""
- QLabel {{
- color: {text_color_str};
- }}
- """)
-
- # 使用 QPalette 设置窗口背景色
- palette = self.palette()
- palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name()))
- self.setPalette(palette)
- self.setAutoFillBackground(True)
-
- # 更新标题标签颜色(如果存在)
- if hasattr(self, '_title_label') and self._title_label:
- self._title_label.setStyleSheet(f"color: {text_color_str}; font-size: 13px; font-weight: 500;")
-
- # 更新关闭按钮颜色
- self._update_close_button_color(text_color)
-
def setup_ui(self):
"""设置界面布局"""
layout = QVBoxLayout(self)
diff --git a/ui/gradient_extract.py b/ui/gradient_extract.py
index 271da41..070b2bf 100644
--- a/ui/gradient_extract.py
+++ b/ui/gradient_extract.py
@@ -584,7 +584,8 @@ class GradientExtractInterface(QWidget):
# 将HEX转换为RGB
r, g, b = self._hex_to_rgb(current_color)
- dialog = ColorPickerDialog((r, g, b), self)
+ # 使用顶层窗口作为父窗口,避免背景色异常和两套窗口控制器
+ dialog = ColorPickerDialog((r, g, b), self.window())
if dialog.exec() == QDialog.DialogCode.Accepted:
color_info = dialog.get_color_info()
if color_info:
diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md"
index 362593d..954f936 100644
--- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md"
+++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md"
@@ -706,7 +706,61 @@ def test_queued_connection_safety(qtbot):
将传统 `QDialog` 改造为 `FramelessDialog`,实现 Fluent Design 风格的自定义标题栏,获得更现代、统一的界面效果。
-### 10.2 核心改造步骤
+### 10.1.1 推荐使用 BaseFramelessDialog 基类
+
+**项目已提供统一的 `BaseFramelessDialog` 基类**,封装了完整的标题栏和主题适配功能。新对话框应直接继承此基类,无需重复实现。
+
+**文件位置:** `dialogs/base_frameless_dialog.py`
+
+**使用方法:**
+
+```python
+from dialogs import BaseFramelessDialog
+from qfluentwidgets import qconfig
+
+class MyDialog(BaseFramelessDialog):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setWindowTitle("我的对话框")
+ self.setFixedSize(400, 300)
+
+ # 设置标题栏和样式(基类提供)
+ self._setup_title_bar()
+ self._update_styles()
+
+ # 设置界面
+ self.setup_ui()
+
+ # 监听主题变化
+ self._theme_connection = qconfig.themeChangedFinished.connect(
+ self._update_styles
+ )
+
+ def closeEvent(self, event):
+ # 断开信号连接
+ try:
+ qconfig.themeChangedFinished.disconnect(self._theme_connection)
+ except (TypeError, RuntimeError):
+ pass
+ super().closeEvent(event)
+
+ def setup_ui(self):
+ """设置界面"""
+ from PySide6.QtWidgets import QVBoxLayout
+ layout = QVBoxLayout(self)
+ # 顶部边距40px为标题栏留出空间
+ layout.setContentsMargins(20, 40, 20, 20)
+ # ... 其他控件
+```
+
+**关键要点:**
+- 继承 `BaseFramelessDialog` 而非直接使用 `FramelessDialog`
+- 调用 `self._setup_title_bar()` 设置标题栏
+- 调用 `self._update_styles()` 初始化样式
+- 布局顶部边距设置为 40px
+- 父窗口使用 `self.window()`
+
+### 10.2 核心改造步骤(旧对话框迁移参考)
#### 10.2.1 依赖安装
@@ -946,6 +1000,8 @@ for widget in [title_label, spacer_label, logo_label, left_spacer]:
| Logo/标题/按钮挤在一起 | 清除了原有布局的所有项 | 保留原有布局,只在开头插入新控件 |
| 标题紧贴左边框 | 未设置左边距 | 添加固定宽度的占位符 QLabel |
| 从子控件打开对话框时背景色异常 | 父窗口不是顶层窗口 | 使用 `parent=self.window()` 而不是 `parent=self` |
+| 出现两套窗口控制器(最小化/最大化/关闭按钮) | 父控件不是窗口,导致 Windows 显示系统按钮 | 确保 `parent` 参数传入顶层窗口(`self.window()`) |
+| 深色模式下背景仍为白色/灰色 | `QPalette` 设置时机不对或父窗口问题 | 1. 确保使用 `QPalette` + `setAutoFillBackground(True)` 设置背景
2. 确保父窗口是顶层窗口
3. 在 `_update_styles` 中设置,而非 `__init__` 中 |
### 10.7 主题颜色函数(theme_colors.py)
@@ -969,8 +1025,18 @@ def get_close_button_pressed_color():
### 10.8 改造检查清单
+#### 新对话框(推荐)
+- [ ] 继承 `BaseFramelessDialog` 基类
+- [ ] 调用 `_setup_title_bar()` 设置标题栏
+- [ ] 调用 `_update_styles()` 初始化样式
+- [ ] 调整布局边距为标题栏留出空间(顶部40px)
+- [ ] 监听主题变化信号
+- [ ] 在 `closeEvent` 中断开信号连接
+- [ ] **关键**:确保对话框父窗口是顶层窗口(`parent=self.window()`)
+
+#### 旧对话框迁移(参考)
- [ ] 安装 PySideSix-Frameless-Window 依赖
-- [ ] 修改类继承为 `FramelessDialog`
+- [ ] 修改类继承为 `FramelessDialog`(或迁移到 `BaseFramelessDialog`)
- [ ] 移除 `setWindowFlags` 调用
- [ ] 移除 `set_window_title_bar_theme` 相关代码
- [ ] 移除 `_fix_taskbar_icon` 调用(FramelessDialog 自动处理)
@@ -981,27 +1047,29 @@ def get_close_button_pressed_color():
- [ ] 调整布局边距为标题栏留出空间
- [ ] 监听主题变化信号
- [ ] 在 `closeEvent` 中断开信号连接
-- [ ] 确保对话框父窗口是顶层窗口(`parent=self.window()`)
-- [ ] 测试深色/浅色主题切换
+- [ ] **关键**:确保对话框父窗口是顶层窗口(`parent=self.window()`)
+- [ ] **关键**:测试从子控件打开对话框时背景色是否正常
+- [ ] **关键**:测试深色/浅色主题切换时关闭按钮颜色是否正常
- [ ] 测试窗口拖动和调整大小功能
### 10.9 后续对话框改造模板
+#### 推荐模板(使用 BaseFramelessDialog 基类)
+
```python
-from qframelesswindow import FramelessDialog
-from PySide6.QtGui import QPalette, QColor
-from utils.theme_colors import get_text_color, get_dialog_bg_color
+from dialogs import BaseFramelessDialog
+from qfluentwidgets import qconfig
+
+class CustomDialog(BaseFramelessDialog):
+ """自定义对话框"""
-class CustomDialog(FramelessDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("对话框标题")
self.setFixedSize(400, 300)
- # 设置自定义标题栏
+ # 设置标题栏和样式(基类提供)
self._setup_title_bar()
-
- # 初始化样式
self._update_styles()
# 设置界面
@@ -1012,97 +1080,12 @@ class CustomDialog(FramelessDialog):
self._update_styles
)
- def _setup_title_bar(self):
- """参考 10.3.2 实现"""
- from PySide6.QtWidgets import QLabel
- from PySide6.QtCore import Qt
- from PySide6.QtGui import QPixmap
- from utils.icon import get_icon_path
- from utils.theme_colors import get_text_color
-
- title_bar = self.titleBar
- h_layout = title_bar.layout()
- if not h_layout:
- return
-
- text_color = get_text_color()
- text_color_str = text_color.name()
-
- # 创建标题栏控件
- left_spacer = QLabel(title_bar)
- left_spacer.setFixedWidth(10)
-
- icon_path = get_icon_path()
- logo_label = None
- if icon_path:
- logo_label = QLabel(title_bar)
- pixmap = QPixmap(icon_path)
- if not pixmap.isNull():
- logo_label.setPixmap(pixmap.scaled(
- 20, 20,
- Qt.AspectRatioMode.KeepAspectRatio,
- Qt.TransformationMode.SmoothTransformation
- ))
-
- spacer_label = QLabel(title_bar)
- spacer_label.setFixedWidth(8)
-
- title_label = QLabel(self.windowTitle(), title_bar)
- title_label.setStyleSheet(
- f"color: {text_color_str}; font-size: 13px; font-weight: 500;"
- )
-
- for widget in [title_label, spacer_label, logo_label, left_spacer]:
- if widget:
- h_layout.insertWidget(0, widget)
- h_layout.setAlignment(widget, Qt.AlignmentFlag.AlignVCenter)
-
- self._title_label = title_label
-
- def _update_close_button_color(self, text_color):
- """参考 10.4.2 实现"""
- from utils.theme_colors import (
- get_close_button_hover_bg_color,
- get_close_button_hover_color,
- get_close_button_pressed_color
- )
-
- title_bar = self.titleBar
- if hasattr(title_bar, 'closeBtn') and title_bar.closeBtn:
- close_btn = title_bar.closeBtn
- close_btn.setNormalColor(text_color)
- close_btn.setHoverColor(get_close_button_hover_color())
- close_btn.setHoverBackgroundColor(get_close_button_hover_bg_color())
- close_btn.setPressedColor(get_close_button_pressed_color())
-
- def _update_styles(self):
- """参考 10.4.3 实现"""
- text_color = get_text_color()
- text_color_str = text_color.name()
- bg_color = get_dialog_bg_color()
-
- self.setStyleSheet(f"""
- QLabel {{
- color: {text_color_str};
- }}
- """)
-
- palette = self.palette()
- palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name()))
- self.setPalette(palette)
- self.setAutoFillBackground(True)
-
- if hasattr(self, '_title_label') and self._title_label:
- self._title_label.setStyleSheet(
- f"color: {text_color_str}; font-size: 13px; font-weight: 500;"
- )
-
- self._update_close_button_color(text_color)
-
def setup_ui(self):
"""设置界面"""
+ from PySide6.QtWidgets import QVBoxLayout
layout = QVBoxLayout(self)
- layout.setContentsMargins(20, 40, 20, 20) # 注意顶部边距
+ # 顶部边距40px为标题栏留出空间
+ layout.setContentsMargins(20, 40, 20, 20)
# ... 其他控件
def closeEvent(self, event):
@@ -1114,4 +1097,18 @@ class CustomDialog(FramelessDialog):
pass
delattr(self, '_theme_connection')
super().closeEvent(event)
+
+
+# ==================== 使用示例 ====================
+
+# ✅ 正确:从子控件打开对话框时,使用 self.window() 作为父窗口
+dialog = CustomDialog(parent=self.window())
+dialog.exec()
+
+# ❌ 错误:直接使用 self 作为父窗口(self 是子控件而非窗口)
+# dialog = CustomDialog(parent=self) # 会导致背景色异常和两套窗口控制器
```
+
+#### 旧模板(直接使用 FramelessDialog,供参考)
+
+如需自定义标题栏样式或实现特殊需求,可直接继承 `FramelessDialog`,参考 10.3-10.4 节实现相关方法。
diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md"
index 107b29e..cbc82e8 100644
--- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md"
+++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md"
@@ -79,6 +79,7 @@ color_card/
├── dialogs/ # 对话框模块目录
│ ├── __init__.py
│ ├── about_dialog.py # 关于对话框
+│ ├── base_frameless_dialog.py # 无边框对话框基类(BaseFramelessDialog,提供统一标题栏和主题适配)
│ ├── colorblind_dialog.py # 色盲模拟预览对话框
│ ├── contrast_dialog.py # 对比度检查对话框
│ ├── edit_palette.py # 配色编辑对话框(EditPaletteDialog、ColorPickerDialog、PresetGrid、ColorModeSliders、GradientSlider、ColorPreview)
@@ -492,6 +493,103 @@ class MainWindow(FluentWindow):
self.addSubInterface(self.interface, FluentIcon.PALETTE, "界面名称")
```
+### 5.1.1 无边框对话框基类使用规范
+
+**使用 `BaseFramelessDialog` 作为无边框对话框基类:**
+
+项目提供统一的 `BaseFramelessDialog` 基类,封装了 Fluent Design 风格的自定义标题栏和主题适配功能。
+
+**文件位置:** `dialogs/base_frameless_dialog.py`
+
+**使用步骤:**
+
+1. **继承基类**
+```python
+from dialogs import BaseFramelessDialog
+
+class MyDialog(BaseFramelessDialog):
+ """我的对话框"""
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setWindowTitle("对话框标题")
+ self.setFixedSize(400, 300)
+```
+
+2. **设置标题栏和样式**
+```python
+# 设置自定义标题栏(显示Logo和标题)
+self._setup_title_bar()
+
+# 初始化样式(背景色、文字颜色等)
+self._update_styles()
+```
+
+3. **监听主题变化**
+```python
+from qfluentwidgets import qconfig
+
+# 在 __init__ 中连接信号
+self._theme_connection = qconfig.themeChangedFinished.connect(
+ self._update_styles
+)
+```
+
+4. **断开信号连接**
+```python
+def closeEvent(self, event):
+ """关闭事件 - 断开信号连接"""
+ try:
+ qconfig.themeChangedFinished.disconnect(self._theme_connection)
+ except (TypeError, RuntimeError):
+ pass
+ super().closeEvent(event)
+```
+
+**完整示例:**
+```python
+from dialogs import BaseFramelessDialog
+from qfluentwidgets import qconfig
+
+class CustomDialog(BaseFramelessDialog):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setWindowTitle("自定义对话框")
+ self.setFixedSize(400, 300)
+
+ # 设置标题栏和样式
+ self._setup_title_bar()
+ self._update_styles()
+
+ # 设置界面
+ self.setup_ui()
+
+ # 监听主题变化
+ self._theme_connection = qconfig.themeChangedFinished.connect(
+ self._update_styles
+ )
+
+ def closeEvent(self, event):
+ try:
+ qconfig.themeChangedFinished.disconnect(self._theme_connection)
+ except (TypeError, RuntimeError):
+ pass
+ super().closeEvent(event)
+
+ def setup_ui(self):
+ """设置界面布局"""
+ from PySide6.QtWidgets import QVBoxLayout
+ layout = QVBoxLayout(self)
+ # 顶部边距40px为标题栏留出空间
+ layout.setContentsMargins(20, 40, 20, 20)
+ # ... 其他控件
+```
+
+**关键要点:**
+- 必须调用 `_setup_title_bar()` 设置自定义标题栏
+- 必须调用 `_update_styles()` 初始化样式
+- 布局边距顶部设置为 40px,为标题栏留出空间
+- 父窗口必须是顶层窗口:`parent=self.window()`
+
### 5.2 界面组织规范
- 每个功能模块创建独立的 `QWidget` 子类
@@ -1959,6 +2057,7 @@ def _get_about_text(self):
| 版本 | 日期 | 变更内容 |
| :--: | :--------: | :------------------------------------------------------------------------: |
+| 3.42 | 2026-03-16 | 新增无边框对话框基类规范(5.1.1节):提取 BaseFramelessDialog 到独立文件,统一标题栏和主题适配;更新项目结构(1.3节) |
| 3.41 | 2026-03-13 | 精简开发规范中的代码示例,删除冗余注释和重复内容,使文档更加简洁易读 |
| 3.40 | 2026-03-07 | 更新项目结构(1.3节):新增 export\_settings\_dialog.py 到 dialogs/ 目录 |
| 3.39 | 2026-03-04 | 新增资源路径规范(2.4节):说明 PyInstaller 打包后的资源路径获取方式,避免打包后资源加载失败 |
--
Gitee
From 9749db5824d12574d73ffa0c266e56eed2ab3253 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?=
Date: Mon, 16 Mar 2026 21:29:09 +0800
Subject: [PATCH 03/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E5=85=B3=E4=BA=8E?=
=?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=A1=86=E6=94=B9=E9=80=A0=E4=B8=BA=E6=97=A0?=
=?UTF-8?q?=E8=BE=B9=E6=A1=86=E7=AA=97=E5=8F=A3=E5=B9=B6=E4=BC=98=E5=8C=96?=
=?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将 AboutDialog 从 QDialog 改造为 BaseFramelessDialog,实现 Fluent Design 风格标题栏
- 添加自定义标题栏设置和主题适配功能
- 设置文本编辑器背景和边框透明,文字颜色根据主题自动切换
- 删除冗余的 _get_base_path 函数,复用 utils 模块中的版本
- 合并 _open_license_file 和 _open_agreement_file 为通用的 _open_file_or_url 方法
- 调整布局边距为顶部 40px,为无边框标题栏留出空间
- 添加 closeEvent 断开主题变化信号连接,避免内存泄漏
---
dialogs/about_dialog.py | 148 ++++++++++++++++------------------------
utils/__init__.py | 3 +-
2 files changed, 59 insertions(+), 92 deletions(-)
diff --git a/dialogs/about_dialog.py b/dialogs/about_dialog.py
index 687f526..641de53 100644
--- a/dialogs/about_dialog.py
+++ b/dialogs/about_dialog.py
@@ -1,40 +1,19 @@
# 标准库导入
-import os
-import sys
from pathlib import Path
# 第三方库导入
-from PySide6.QtCore import Qt, QTimer, QUrl
+from PySide6.QtCore import Qt, QUrl
from PySide6.QtGui import QDesktopServices
-from PySide6.QtWidgets import (
- QDialog, QFrame, QHBoxLayout, QVBoxLayout, QWidget
-)
+from PySide6.QtWidgets import QHBoxLayout, QVBoxLayout, QWidget
from qfluentwidgets import CaptionLabel, PlainTextEdit, PushButton, isDarkTheme, qconfig
# 项目模块导入
-from utils import tr, fix_windows_taskbar_icon_for_window, load_icon_universal, set_window_title_bar_theme
+from utils import tr, load_icon_universal, get_base_path
from version import version_manager
-from utils.theme_colors import get_dialog_bg_color, get_text_color
+from dialogs.base_frameless_dialog import BaseFramelessDialog
-def _get_base_path() -> str:
- """获取应用程序基础路径
-
- 支持开发环境和 PyInstaller 打包后的环境
-
- Returns:
- str: 应用程序基础路径
- """
- if getattr(sys, 'frozen', False):
- # PyInstaller 打包后的环境
- if hasattr(sys, '_MEIPASS'):
- return sys._MEIPASS
- return os.path.dirname(sys.executable)
- # 开发环境 - 返回项目根目录
- return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
-
-class AboutDialog(QDialog):
+class AboutDialog(BaseFramelessDialog):
"""关于对话框
显示应用程序信息、版本信息、开发团队信息、
@@ -49,30 +28,24 @@ class AboutDialog(QDialog):
# 设置窗口图标
self.setWindowIcon(load_icon_universal())
- # 设置窗口标志:只保留关闭按钮(必须在设置窗口标题之后)
- self.setWindowFlags(
- Qt.WindowType.Window |
- Qt.WindowType.WindowTitleHint |
- Qt.WindowType.WindowCloseButtonHint |
- Qt.WindowType.CustomizeWindowHint
- )
+ # 设置自定义标题栏
+ self._setup_title_bar()
- # 设置窗口背景色(与 FluentWindow 一致)
- bg_color = get_dialog_bg_color()
- self.setStyleSheet(f"QDialog {{ background-color: {bg_color.name()}; }}")
+ # 初始化样式
+ self._update_styles()
+ # 设置界面
self.setup_ui()
- # 修复任务栏图标(在窗口显示后调用)
- QTimer.singleShot(100, lambda: fix_windows_taskbar_icon_for_window(self))
-
# 监听主题变化
- qconfig.themeChangedFinished.connect(self._update_title_bar_theme)
+ self._theme_connection = qconfig.themeChangedFinished.connect(
+ self._update_styles
+ )
def setup_ui(self):
"""设置界面布局"""
layout = QVBoxLayout(self)
- layout.setContentsMargins(20, 20, 20, 20)
+ layout.setContentsMargins(20, 40, 20, 20)
layout.setSpacing(15)
# 内容区域
@@ -86,7 +59,7 @@ class AboutDialog(QDialog):
def _create_content_area(self, parent_layout):
"""创建内容显示区域
-
+
Args:
parent_layout: 父布局对象
"""
@@ -94,24 +67,24 @@ class AboutDialog(QDialog):
self.text_edit.setReadOnly(True)
self.text_edit.setPlainText(self._get_about_text())
self.text_edit.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)
- # 禁用焦点,去除底部蓝色条
self.text_edit.setFocusPolicy(Qt.FocusPolicy.NoFocus)
-
- # 设置主题感知的样式
- bg_color = get_dialog_bg_color()
- text_color = get_text_color()
- self.text_edit.setStyleSheet(
- f"PlainTextEdit {{ background-color: {bg_color.name()}; "
- f"color: {text_color.name()}; border: none; }}\n"
- f"PlainTextEdit:focus {{ border: none; outline: none; }}\n"
- f"PlainTextEdit::focus {{ border: none; }}\n"
- f"QPlainTextEdit {{ border: none; }}\n"
- f"QPlainTextEdit:focus {{ border: none; outline: none; }}"
- )
-
- parent_layout.addWidget(self.text_edit, stretch=1)
+ # 设置背景和边框透明,文字颜色根据主题变化
+ text_color = "#ffffff" if isDarkTheme() else "#333333"
+ self.text_edit.setStyleSheet(f"""
+ PlainTextEdit {{
+ background-color: transparent;
+ border: none;
+ color: {text_color};
+ }}
+ QPlainTextEdit {{
+ background-color: transparent;
+ border: none;
+ color: {text_color};
+ }}
+ """)
+ parent_layout.addWidget(self.text_edit, stretch=1)
def _create_buttons_area(self, parent_layout):
"""创建按钮区域
@@ -196,33 +169,27 @@ class AboutDialog(QDialog):
"""
QDesktopServices.openUrl(QUrl(url))
- def _open_license_file(self):
- """打开开源许可文件"""
- # 获取许可证文件路径(相对于项目根目录的 file/LICENSE.html)
- base_path = _get_base_path()
- license_path = Path(base_path) / "file" / "LICENSE.html"
-
- if license_path.exists():
- # 转换为文件URL并打开
- file_url = QUrl.fromLocalFile(str(license_path.absolute()))
- QDesktopServices.openUrl(file_url)
+ def _open_file_or_url(self, filename: str, fallback_url: str) -> None:
+ """打开本地文件,不存在则打开URL
+
+ Args:
+ filename: 文件名(位于 file/ 目录下)
+ fallback_url: 文件不存在时的备用URL
+ """
+ file_path = Path(get_base_path()) / "file" / filename
+
+ if file_path.exists():
+ QDesktopServices.openUrl(QUrl.fromLocalFile(str(file_path.absolute())))
else:
- # 如果文件不存在,打开项目地址
- self._open_url("https://gitee.com/qingshangongzai/color_card")
+ self._open_url(fallback_url)
+
+ def _open_license_file(self) -> None:
+ """打开开源许可文件"""
+ self._open_file_or_url("LICENSE.html", "https://gitee.com/qingshangongzai/color_card")
- def _open_agreement_file(self):
+ def _open_agreement_file(self) -> None:
"""打开用户协议文件"""
- # 获取用户协议文件路径(相对于项目根目录的 file/UserAgreement.html)
- base_path = _get_base_path()
- agreement_path = Path(base_path) / "file" / "UserAgreement.html"
-
- if agreement_path.exists():
- # 转换为文件URL并打开
- file_url = QUrl.fromLocalFile(str(agreement_path.absolute()))
- QDesktopServices.openUrl(file_url)
- else:
- # 如果文件不存在,打开项目地址
- self._open_url("https://gitee.com/qingshangongzai/color-card")
+ self._open_file_or_url("UserAgreement.html", "https://gitee.com/qingshangongzai/color-card")
def _get_about_text(self):
"""获取关于页面的文本内容"""
@@ -380,16 +347,15 @@ class AboutDialog(QDialog):
• 感谢Adobe Color、色采、palettemakel等优秀产品为我们提供的灵感和参考
"""
- def _update_title_bar_theme(self):
- """更新标题栏主题以适配当前主题"""
- set_window_title_bar_theme(self, isDarkTheme())
-
- def showEvent(self, event):
- """窗口显示事件 - 在显示前设置标题栏主题避免闪烁"""
- # 先设置标题栏主题(在父类 showEvent 之前)
- self._update_title_bar_theme()
- # 调用父类的 showEvent
- super().showEvent(event)
+ def closeEvent(self, event):
+ """关闭事件 - 断开信号连接"""
+ if hasattr(self, '_theme_connection'):
+ try:
+ qconfig.themeChangedFinished.disconnect(self._theme_connection)
+ except (TypeError, RuntimeError):
+ pass
+ delattr(self, '_theme_connection')
+ super().closeEvent(event)
def contextMenuEvent(self, event):
"""屏蔽原生右键菜单"""
diff --git a/utils/__init__.py b/utils/__init__.py
index 622ca68..1fb9c18 100644
--- a/utils/__init__.py
+++ b/utils/__init__.py
@@ -1,6 +1,6 @@
"""工具函数模块"""
-from .icon import load_icon_universal, get_icon_path, create_fallback_icon
+from .icon import load_icon_universal, get_icon_path, create_fallback_icon, get_base_path
from .platform import set_app_user_model_id, fix_windows_taskbar_icon_for_window, set_window_title_bar_theme
from .layout import calculate_grid_columns
from .locale import (
@@ -16,6 +16,7 @@ __all__ = [
'load_icon_universal',
'get_icon_path',
'create_fallback_icon',
+ 'get_base_path',
'set_app_user_model_id',
'fix_windows_taskbar_icon_for_window',
'set_window_title_bar_theme',
--
Gitee
From 4400e50ec72f33bb92842dc923b39ed1a65f376f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?=
Date: Mon, 16 Mar 2026 21:57:03 +0800
Subject: [PATCH 04/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E5=B0=86=E8=89=B2?=
=?UTF-8?q?=E7=9B=B2=E6=A8=A1=E6=8B=9F=E5=92=8C=E5=AF=B9=E6=AF=94=E5=BA=A6?=
=?UTF-8?q?=E6=A3=80=E6=9F=A5=E5=AF=B9=E8=AF=9D=E6=A1=86=E6=94=B9=E9=80=A0?=
=?UTF-8?q?=E4=B8=BA=E6=97=A0=E8=BE=B9=E6=A1=86=E7=AA=97=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增 BaseFramelessDialog 基类,封装 Fluent Design 风格标题栏和主题适配
- 改造 ColorblindPreviewDialog 继承 BaseFramelessDialog,移除冗余代码
- 改造 ContrastCheckDialog 继承 BaseFramelessDialog,移除冗余代码
- 优化主题切换处理,简化 _update_styles 方法
- 清理未使用的导入和重复的颜色转换
- 移动局部导入到文件顶部,符合代码规范
- 更新开发经验总结文档,完善无边框窗口改造指南
---
dialogs/base_frameless_dialog.py | 20 +++--
dialogs/colorblind_dialog.py | 81 +++++++++---------
dialogs/contrast_dialog.py | 83 +++++++++----------
...17\351\252\214\346\200\273\347\273\223.md" | 54 ++++++------
4 files changed, 114 insertions(+), 124 deletions(-)
diff --git a/dialogs/base_frameless_dialog.py b/dialogs/base_frameless_dialog.py
index ad35f10..977eb58 100644
--- a/dialogs/base_frameless_dialog.py
+++ b/dialogs/base_frameless_dialog.py
@@ -119,23 +119,27 @@ class BaseFramelessDialog(FramelessDialog):
text_color = get_text_color()
text_color_str = text_color.name()
bg_color = get_dialog_bg_color()
+ bg_color_str = bg_color.name()
- # 设置 QLabel 文字颜色样式表
+ # 使用 QPalette 设置窗口背景色
+ palette = self.palette()
+ palette.setColor(QPalette.ColorRole.Window, bg_color)
+ self.setPalette(palette)
+ self.setAutoFillBackground(True)
+
+ # 设置样式表 - QLabel 文字颜色
self.setStyleSheet(f"""
QLabel {{
color: {text_color_str};
+ background-color: transparent;
}}
""")
- # 使用 QPalette 设置窗口背景色
- palette = self.palette()
- palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name()))
- self.setPalette(palette)
- self.setAutoFillBackground(True)
-
# 更新标题标签颜色(如果存在)
if hasattr(self, '_title_label') and self._title_label:
- self._title_label.setStyleSheet(f"color: {text_color_str}; font-size: 13px; font-weight: 500;")
+ self._title_label.setStyleSheet(
+ f"color: {text_color_str}; font-size: 13px; font-weight: 500;"
+ )
# 更新关闭按钮颜色
self._update_close_button_color(text_color)
diff --git a/dialogs/colorblind_dialog.py b/dialogs/colorblind_dialog.py
index 44955fb..e980269 100644
--- a/dialogs/colorblind_dialog.py
+++ b/dialogs/colorblind_dialog.py
@@ -7,23 +7,24 @@
from typing import List, Dict, Tuple
# 第三方库导入
-from PySide6.QtCore import Qt, QTimer
+from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
- QDialog, QHBoxLayout, QLabel, QVBoxLayout, QWidget,
+ QHBoxLayout, QLabel, QVBoxLayout, QWidget,
QFrame
)
from PySide6.QtGui import QColor
from qfluentwidgets import (
- ComboBox, isDarkTheme, qconfig, ScrollArea
+ ComboBox, qconfig, ScrollArea
)
# 项目模块导入
-from utils import tr, fix_windows_taskbar_icon_for_window, load_icon_universal, set_window_title_bar_theme
+from utils import tr, load_icon_universal
+from dialogs import BaseFramelessDialog
from core.colorblind import (
simulate_colorblind, get_colorblind_info, get_all_colorblind_types
)
from utils.theme_colors import (
- get_dialog_bg_color, get_text_color, get_border_color,
+ get_text_color, get_border_color,
get_secondary_text_color, get_title_color
)
@@ -62,13 +63,11 @@ class ColorBlock(QWidget):
class ColorComparisonRow(QWidget):
"""颜色对比行组件 - 显示原颜色和模拟颜色"""
-
+
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
self._update_styles()
- # 监听主题变化
- qconfig.themeChangedFinished.connect(self._update_styles)
def setup_ui(self):
"""设置界面"""
@@ -140,15 +139,15 @@ class ColorComparisonRow(QWidget):
self.simulated_hex.setText(f"{tr('dialogs.colorblind.simulated')}: {simulated_hex}")
-class ColorblindPreviewDialog(QDialog):
+class ColorblindPreviewDialog(BaseFramelessDialog):
"""色盲模拟预览对话框
-
+
显示配色方案在不同色盲类型下的视觉效果。
"""
-
+
def __init__(self, scheme_name: str, colors: List[Dict], parent=None):
"""初始化色盲预览对话框
-
+
Args:
scheme_name: 配色方案名称
colors: 颜色列表,每个颜色是一个字典,包含 'rgb' 键
@@ -158,38 +157,32 @@ class ColorblindPreviewDialog(QDialog):
self._scheme_name = scheme_name
self._colors = colors
self._current_type = 'normal'
-
+
self.setWindowTitle(tr('dialogs.colorblind.window_title', name=scheme_name))
self.setFixedSize(320, 420)
-
+
# 设置窗口图标
self.setWindowIcon(load_icon_universal())
-
- # 设置窗口标志
- self.setWindowFlags(
- Qt.WindowType.Window |
- Qt.WindowType.WindowTitleHint |
- Qt.WindowType.WindowCloseButtonHint |
- Qt.WindowType.CustomizeWindowHint
- )
-
- # 设置窗口背景色
- bg_color = get_dialog_bg_color()
- self.setStyleSheet(f"QDialog {{ background-color: {bg_color.name()}; }}")
-
+
+ # 设置界面
self.setup_ui()
+
+ # 设置标题栏和样式
+ self._setup_title_bar()
+ self._update_styles()
+
+ # 更新预览
self.update_preview()
-
- # 修复任务栏图标
- QTimer.singleShot(100, lambda: fix_windows_taskbar_icon_for_window(self))
-
+
# 监听主题变化
- qconfig.themeChangedFinished.connect(self._update_title_bar_theme)
+ self._theme_connection = qconfig.themeChangedFinished.connect(
+ self._update_styles
+ )
def setup_ui(self):
"""设置界面布局"""
main_layout = QVBoxLayout(self)
- main_layout.setContentsMargins(20, 20, 20, 20)
+ main_layout.setContentsMargins(20, 40, 20, 20)
main_layout.setSpacing(15)
# 获取主题颜色
@@ -281,9 +274,8 @@ class ColorblindPreviewDialog(QDialog):
}
""")
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
-
+
self.comparison_container = QWidget()
- self.comparison_container.setStyleSheet("background: transparent;")
self.comparison_layout = QVBoxLayout(self.comparison_container)
self.comparison_layout.setContentsMargins(0, 0, 0, 0)
self.comparison_layout.setSpacing(5)
@@ -328,12 +320,13 @@ class ColorblindPreviewDialog(QDialog):
# 更新说明文字
info = get_colorblind_info(self._current_type)
self.description_label.setText(tr('dialogs.colorblind.description', text=info['description']))
-
- def _update_title_bar_theme(self):
- """更新标题栏主题以适配当前主题"""
- set_window_title_bar_theme(self, isDarkTheme())
-
- def showEvent(self, event):
- """窗口显示事件"""
- self._update_title_bar_theme()
- super().showEvent(event)
+
+ def closeEvent(self, event):
+ """关闭事件:断开信号连接"""
+ if hasattr(self, '_theme_connection'):
+ try:
+ qconfig.themeChangedFinished.disconnect(self._theme_connection)
+ except (TypeError, RuntimeError):
+ pass
+ delattr(self, '_theme_connection')
+ super().closeEvent(event)
diff --git a/dialogs/contrast_dialog.py b/dialogs/contrast_dialog.py
index 5341804..bae3a6b 100644
--- a/dialogs/contrast_dialog.py
+++ b/dialogs/contrast_dialog.py
@@ -8,25 +8,26 @@
from typing import List, Dict, Tuple
# 第三方库导入
-from PySide6.QtCore import Qt, QTimer, Signal
+from PySide6.QtCore import Qt, Signal, QPointF
from PySide6.QtWidgets import (
- QDialog, QHBoxLayout, QLabel, QVBoxLayout, QWidget,
- QFrame, QGridLayout, QScrollArea
+ QHBoxLayout, QLabel, QVBoxLayout, QWidget,
+ QFrame, QScrollArea
)
-from PySide6.QtGui import QColor, QPainter, QBrush, QPen, QFont
+from PySide6.QtGui import QColor, QPainter, QBrush, QPen, QFont, QPolygonF
from qfluentwidgets import (
ComboBox, PushButton, ToolButton, FluentIcon,
isDarkTheme, qconfig, CardWidget
)
# 项目模块导入
-from utils import tr, fix_windows_taskbar_icon_for_window, load_icon_universal, set_window_title_bar_theme
+from utils import tr, load_icon_universal
+from dialogs import BaseFramelessDialog
from core.contrast import (
calculate_contrast_ratio, get_contrast_info,
rgb_to_hex, get_contrast_status_color
)
from utils.theme_colors import (
- get_dialog_bg_color, get_text_color, get_border_color,
+ get_text_color, get_border_color,
get_secondary_text_color, get_title_color, get_card_background_color
)
@@ -272,8 +273,6 @@ class GraphicWidget(QWidget):
painter.drawEllipse(50, 20, 20, 20)
# 绘制三角形
- from PySide6.QtGui import QPolygonF
- from PySide6.QtCore import QPointF
triangle = QPolygonF([
QPointF(90, 20),
QPointF(80, 40),
@@ -350,16 +349,16 @@ class GraphicPreviewCard(QWidget):
painter.drawRoundedRect(0, 0, self.width() - 1, self.height() - 1, 8, 8)
-class ContrastCheckDialog(QDialog):
+class ContrastCheckDialog(BaseFramelessDialog):
"""对比度检查对话框
-
+
允许用户选择配色方案中的两种颜色进行对比度检查,
并实时预览文字可读性效果。
"""
-
+
def __init__(self, scheme_name: str, colors: List[Dict], parent=None):
"""初始化对比度检查对话框
-
+
Args:
scheme_name: 配色方案名称
colors: 颜色列表,每个颜色是一个字典,包含 'rgb' 键
@@ -368,43 +367,36 @@ class ContrastCheckDialog(QDialog):
super().__init__(parent)
self._scheme_name = scheme_name
self._colors = colors
-
+
self.setWindowTitle(tr('dialogs.contrast.window_title', name=scheme_name))
-
+
# 设置窗口图标
self.setWindowIcon(load_icon_universal())
-
- # 设置窗口标志 - 使用 MSWindowsFixedSizeDialogHint 防止 Windows 自动调整大小
- self.setWindowFlags(
- Qt.WindowType.Window |
- Qt.WindowType.WindowTitleHint |
- Qt.WindowType.WindowCloseButtonHint |
- Qt.WindowType.CustomizeWindowHint |
- Qt.WindowType.MSWindowsFixedSizeDialogHint
- )
-
- # 设置固定大小(使用最小/最大尺寸确保不被压缩)
+
+ # 设置固定大小
self.setMinimumSize(480, 420)
self.setMaximumSize(480, 420)
self.resize(480, 420)
-
- # 设置窗口背景色
- bg_color = get_dialog_bg_color()
- self.setStyleSheet(f"QDialog {{ background-color: {bg_color.name()}; }}")
-
+
+ # 设置界面
self.setup_ui()
+
+ # 设置标题栏和样式
+ self._setup_title_bar()
+ self._update_styles()
+
+ # 更新对比度
self._update_contrast()
-
- # 修复任务栏图标
- QTimer.singleShot(100, lambda: fix_windows_taskbar_icon_for_window(self))
-
+
# 监听主题变化
- qconfig.themeChangedFinished.connect(self._update_title_bar_theme)
+ self._theme_connection = qconfig.themeChangedFinished.connect(
+ self._update_styles
+ )
def setup_ui(self):
"""设置界面布局"""
main_layout = QVBoxLayout(self)
- main_layout.setContentsMargins(20, 20, 20, 20)
+ main_layout.setContentsMargins(20, 40, 20, 20)
main_layout.setSpacing(15)
# 获取主题颜色
@@ -591,12 +583,13 @@ class ContrastCheckDialog(QDialog):
self.normal_preview.set_colors(bg_rgb, text_rgb)
self.large_preview.set_colors(bg_rgb, text_rgb)
self.graphic_preview.set_colors(bg_rgb, text_rgb)
-
- def _update_title_bar_theme(self):
- """更新标题栏主题以适配当前主题"""
- set_window_title_bar_theme(self, isDarkTheme())
-
- def showEvent(self, event):
- """窗口显示事件"""
- self._update_title_bar_theme()
- super().showEvent(event)
+
+ def closeEvent(self, event):
+ """关闭事件:断开信号连接"""
+ if hasattr(self, '_theme_connection'):
+ try:
+ qconfig.themeChangedFinished.disconnect(self._theme_connection)
+ except (TypeError, RuntimeError):
+ pass
+ delattr(self, '_theme_connection')
+ super().closeEvent(event)
diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md"
index 954f936..fbecff2 100644
--- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md"
+++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md"
@@ -717,6 +717,7 @@ def test_queued_connection_safety(qtbot):
```python
from dialogs import BaseFramelessDialog
from qfluentwidgets import qconfig
+from PySide6.QtWidgets import QVBoxLayout
class MyDialog(BaseFramelessDialog):
def __init__(self, parent=None):
@@ -724,29 +725,30 @@ class MyDialog(BaseFramelessDialog):
self.setWindowTitle("我的对话框")
self.setFixedSize(400, 300)
+ # 设置界面
+ self.setup_ui()
+
# 设置标题栏和样式(基类提供)
self._setup_title_bar()
self._update_styles()
- # 设置界面
- self.setup_ui()
-
# 监听主题变化
self._theme_connection = qconfig.themeChangedFinished.connect(
self._update_styles
)
def closeEvent(self, event):
- # 断开信号连接
- try:
- qconfig.themeChangedFinished.disconnect(self._theme_connection)
- except (TypeError, RuntimeError):
- pass
+ """关闭事件:断开信号连接"""
+ if hasattr(self, '_theme_connection'):
+ try:
+ qconfig.themeChangedFinished.disconnect(self._theme_connection)
+ except (TypeError, RuntimeError):
+ pass
+ delattr(self, '_theme_connection')
super().closeEvent(event)
def setup_ui(self):
"""设置界面"""
- from PySide6.QtWidgets import QVBoxLayout
layout = QVBoxLayout(self)
# 顶部边距40px为标题栏留出空间
layout.setContentsMargins(20, 40, 20, 20)
@@ -755,10 +757,10 @@ class MyDialog(BaseFramelessDialog):
**关键要点:**
- 继承 `BaseFramelessDialog` 而非直接使用 `FramelessDialog`
-- 调用 `self._setup_title_bar()` 设置标题栏
-- 调用 `self._update_styles()` 初始化样式
-- 布局顶部边距设置为 40px
-- 父窗口使用 `self.window()`
+- 先调用 `setup_ui()` 设置界面,再调用 `_setup_title_bar()` 和 `_update_styles()`
+- 布局顶部边距设置为 40px,为标题栏留出空间
+- 父窗口使用 `self.window()` 传递顶层窗口
+- 在 `closeEvent` 中断开主题变化信号连接
### 10.2 核心改造步骤(旧对话框迁移参考)
@@ -934,26 +936,24 @@ def _update_close_button_color(self, text_color):
```python
def _update_styles(self):
"""更新样式以适配主题"""
- from PySide6.QtGui import QPalette, QColor
- from utils.theme_colors import get_text_color, get_dialog_bg_color
-
text_color = get_text_color()
text_color_str = text_color.name()
bg_color = get_dialog_bg_color()
- # 设置 QLabel 文字颜色样式表
+ # 使用 QPalette 设置窗口背景色
+ palette = self.palette()
+ palette.setColor(QPalette.ColorRole.Window, bg_color)
+ self.setPalette(palette)
+ self.setAutoFillBackground(True)
+
+ # 设置样式表 - QLabel 文字颜色
self.setStyleSheet(f"""
QLabel {{
color: {text_color_str};
+ background-color: transparent;
}}
""")
- # 使用 QPalette 设置窗口背景色
- palette = self.palette()
- palette.setColor(QPalette.ColorRole.Window, QColor(bg_color.name()))
- self.setPalette(palette)
- self.setAutoFillBackground(True)
-
# 更新标题标签颜色
if hasattr(self, '_title_label') and self._title_label:
self._title_label.setStyleSheet(
@@ -1059,6 +1059,7 @@ def get_close_button_pressed_color():
```python
from dialogs import BaseFramelessDialog
from qfluentwidgets import qconfig
+from PySide6.QtWidgets import QVBoxLayout
class CustomDialog(BaseFramelessDialog):
"""自定义对话框"""
@@ -1068,13 +1069,13 @@ class CustomDialog(BaseFramelessDialog):
self.setWindowTitle("对话框标题")
self.setFixedSize(400, 300)
+ # 设置界面
+ self.setup_ui()
+
# 设置标题栏和样式(基类提供)
self._setup_title_bar()
self._update_styles()
- # 设置界面
- self.setup_ui()
-
# 监听主题变化
self._theme_connection = qconfig.themeChangedFinished.connect(
self._update_styles
@@ -1082,7 +1083,6 @@ class CustomDialog(BaseFramelessDialog):
def setup_ui(self):
"""设置界面"""
- from PySide6.QtWidgets import QVBoxLayout
layout = QVBoxLayout(self)
# 顶部边距40px为标题栏留出空间
layout.setContentsMargins(20, 40, 20, 20)
--
Gitee
From 2d057675c79057cac518586c366ceb2c623f90b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?=
Date: Mon, 16 Mar 2026 22:06:08 +0800
Subject: [PATCH 05/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E5=B0=86=20Export?=
=?UTF-8?q?SettingsDialog=20=E6=94=B9=E9=80=A0=E4=B8=BA=E6=97=A0=E8=BE=B9?=
=?UTF-8?q?=E6=A1=86=E7=AA=97=E5=8F=A3=E5=B9=B6=E6=B8=85=E7=90=86=E5=86=97?=
=?UTF-8?q?=E4=BD=99=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将 ExportSettingsDialog 从 QDialog 迁移到 BaseFramelessDialog 基类
- 移除 setWindowFlags、set_window_title_bar_theme 等冗余代码
- 删除未使用的导入(QPixmap、isDarkTheme、get_text_color)
- 移除冗余的 _update_styles 方法,完全依赖基类实现
- 调整布局边距为标题栏留出空间(顶部40px)
- 新增 closeEvent 方法断开主题变化信号连接
- 更新开发经验总结文档,添加代码清理与避免冗余章节
---
dialogs/export_settings_dialog.py | 66 +++++++------------
...17\351\252\214\346\200\273\347\273\223.md" | 60 ++++++++++++++++-
2 files changed, 82 insertions(+), 44 deletions(-)
diff --git a/dialogs/export_settings_dialog.py b/dialogs/export_settings_dialog.py
index dd2f3c6..ea2c9f4 100644
--- a/dialogs/export_settings_dialog.py
+++ b/dialogs/export_settings_dialog.py
@@ -4,21 +4,20 @@ from typing import List, Optional
# 第三方库导入
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import (
- QDialog, QHBoxLayout, QLabel, QVBoxLayout, QWidget,
- QFrame, QFileDialog, QMessageBox
+ QHBoxLayout, QLabel, QVBoxLayout, QWidget,
+ QFileDialog, QMessageBox
)
-from PySide6.QtGui import QPixmap
from qfluentwidgets import (
- PushButton, LineEdit, RadioButton, isDarkTheme, qconfig,
+ PushButton, LineEdit, RadioButton, qconfig,
PrimaryPushButton, CheckBox, ScrollArea
)
# 项目模块导入
-from utils import tr, set_window_title_bar_theme
-from utils.theme_colors import get_text_color, get_card_background_color, get_dialog_bg_color
+from dialogs import BaseFramelessDialog
+from utils import tr
-class ExportSettingsDialog(QDialog):
+class ExportSettingsDialog(BaseFramelessDialog):
"""导出设置对话框
用于配置配色预览导出选项,包括选择图片、设置文件名前缀、选择导出格式等。
@@ -39,32 +38,25 @@ class ExportSettingsDialog(QDialog):
self._png_radio: Optional[RadioButton] = None
self.setWindowTitle(tr('dialogs.export_settings.title'))
+ self.setFixedSize(400, 450)
- # 设置窗口标志 - 只保留关闭按钮
- self.setWindowFlags(
- Qt.WindowType.Window |
- Qt.WindowType.WindowTitleHint |
- Qt.WindowType.WindowCloseButtonHint |
- Qt.WindowType.CustomizeWindowHint
- )
-
- # 设置背景色
- bg_color = get_dialog_bg_color()
- self.setStyleSheet(f"QDialog {{ background-color: {bg_color.name()}; }}")
-
+ # 设置界面
self.setup_ui()
+
+ # 设置标题栏和样式(基类提供)
+ self._setup_title_bar()
self._update_styles()
# 监听主题变化
- qconfig.themeChangedFinished.connect(self._update_styles)
-
- # 设置固定大小(必须在 setup_ui 之后)
- self.setFixedSize(400, 450)
+ self._theme_connection = qconfig.themeChangedFinished.connect(
+ self._update_styles
+ )
def setup_ui(self):
"""设置界面"""
layout = QVBoxLayout(self)
- layout.setContentsMargins(20, 20, 20, 20)
+ # 顶部边距40px为标题栏留出空间
+ layout.setContentsMargins(20, 40, 20, 20)
layout.setSpacing(16)
# 选择图片区域
@@ -227,20 +219,12 @@ class ExportSettingsDialog(QDialog):
return "png"
return "svg"
- def showEvent(self, event):
- """窗口显示前设置标题栏主题,避免闪烁"""
- self._update_title_bar_theme()
- super().showEvent(event)
-
- def _update_title_bar_theme(self):
- """更新标题栏主题"""
- set_window_title_bar_theme(self, isDarkTheme())
-
- def _update_styles(self):
- """更新样式以适配主题"""
- text_color = get_text_color()
- text_color_hex = text_color.name()
-
- # 更新所有标签的文字颜色
- for widget in self.findChildren(QLabel):
- widget.setStyleSheet(f"color: {text_color_hex};")
+ def closeEvent(self, event):
+ """关闭事件:断开信号连接"""
+ if hasattr(self, '_theme_connection'):
+ try:
+ qconfig.themeChangedFinished.disconnect(self._theme_connection)
+ except (TypeError, RuntimeError):
+ pass
+ delattr(self, '_theme_connection')
+ super().closeEvent(event)
diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md"
index fbecff2..357c4cf 100644
--- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md"
+++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\347\273\217\351\252\214\346\200\273\347\273\223.md"
@@ -1003,7 +1003,57 @@ for widget in [title_label, spacer_label, logo_label, left_spacer]:
| 出现两套窗口控制器(最小化/最大化/关闭按钮) | 父控件不是窗口,导致 Windows 显示系统按钮 | 确保 `parent` 参数传入顶层窗口(`self.window()`) |
| 深色模式下背景仍为白色/灰色 | `QPalette` 设置时机不对或父窗口问题 | 1. 确保使用 `QPalette` + `setAutoFillBackground(True)` 设置背景
2. 确保父窗口是顶层窗口
3. 在 `_update_styles` 中设置,而非 `__init__` 中 |
-### 10.7 主题颜色函数(theme_colors.py)
+### 10.7 代码清理与避免冗余
+
+#### 10.7.1 不要重复实现基类已有的功能
+
+**❌ 错误方式(子类重复实现样式更新):**
+```python
+class MyDialog(BaseFramelessDialog):
+ def _update_styles(self):
+ """更新样式以适配主题"""
+ # 调用父类方法
+ super()._update_styles()
+
+ # 冗余:基类已通过 setStyleSheet 统一设置 QLabel 颜色
+ text_color = get_text_color()
+ text_color_hex = text_color.name()
+ for widget in self.findChildren(QLabel):
+ widget.setStyleSheet(f"color: {text_color_hex};")
+```
+
+**✅ 正确方式(完全依赖基类):**
+```python
+class MyDialog(BaseFramelessDialog):
+ # 不需要重写 _update_styles,完全使用基类实现
+ pass
+```
+
+**关键原则:**
+- `BaseFramelessDialog._update_styles()` 已通过 `self.setStyleSheet()` 为所有 QLabel 统一设置颜色
+- 子类无需重复设置 QLabel 颜色,除非需要特殊的样式覆盖
+- 保持代码简洁,遵循 DRY 原则
+
+#### 10.7.2 及时清理未使用的导入
+
+改造完成后应检查并删除未使用的导入:
+
+```python
+# 改造前可能需要
+from PySide6.QtGui import QPixmap
+from qfluentwidgets import isDarkTheme
+from utils.theme_colors import get_text_color
+
+# 改造后若未使用,应删除上述导入
+```
+
+**常见可删除的导入(使用 BaseFramelessDialog 后):**
+- `QPixmap` - 若对话框不涉及图片显示
+- `isDarkTheme` - 基类已处理主题检测
+- `get_text_color` / `get_dialog_bg_color` - 基类已处理颜色获取
+- `set_window_title_bar_theme` - 无边框窗口不再需要
+
+### 10.8 主题颜色函数(theme_colors.py)
在 `utils/theme_colors.py` 中添加关闭按钮颜色函数:
@@ -1023,7 +1073,7 @@ def get_close_button_pressed_color():
return QColor(255, 255, 255)
```
-### 10.8 改造检查清单
+### 10.9 改造检查清单
#### 新对话框(推荐)
- [ ] 继承 `BaseFramelessDialog` 基类
@@ -1033,6 +1083,8 @@ def get_close_button_pressed_color():
- [ ] 监听主题变化信号
- [ ] 在 `closeEvent` 中断开信号连接
- [ ] **关键**:确保对话框父窗口是顶层窗口(`parent=self.window()`)
+- [ ] **清理**:删除未使用的导入(`QPixmap`、`isDarkTheme`、`get_text_color` 等)
+- [ ] **清理**:避免重写 `_update_styles`,除非需要特殊样式覆盖
#### 旧对话框迁移(参考)
- [ ] 安装 PySideSix-Frameless-Window 依赖
@@ -1051,8 +1103,10 @@ def get_close_button_pressed_color():
- [ ] **关键**:测试从子控件打开对话框时背景色是否正常
- [ ] **关键**:测试深色/浅色主题切换时关闭按钮颜色是否正常
- [ ] 测试窗口拖动和调整大小功能
+- [ ] **清理**:删除未使用的导入(`QPixmap`、`isDarkTheme`、`set_window_title_bar_theme` 等)
+- [ ] **清理**:检查并删除冗余的 `_update_styles` 重写
-### 10.9 后续对话框改造模板
+### 10.10 后续对话框改造模板
#### 推荐模板(使用 BaseFramelessDialog 基类)
--
Gitee
From 8b06a9ded79ed95b7de3b7dc2defd79b8f12a5c9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?=
Date: Mon, 16 Mar 2026 22:30:31 +0800
Subject: [PATCH 06/47] =?UTF-8?q?[=E9=87=8D=E6=9E=84]=20=E5=B0=86=E6=A3=80?=
=?UTF-8?q?=E6=9F=A5=E6=9B=B4=E6=96=B0=E6=A1=86=E6=94=B9=E9=80=A0=E4=B8=BA?=
=?UTF-8?q?=E6=97=A0=E8=BE=B9=E6=A1=86=E7=AA=97=E5=8F=A3=EF=BC=8C=E7=B2=BE?=
=?UTF-8?q?=E7=AE=80=E4=BA=86=E4=BB=A3=E7=A0=81=EF=BC=8C=E7=BB=9F=E4=B8=80?=
=?UTF-8?q?=E4=BD=BF=E7=94=A8=20BaseFramelessDialog=20=E5=9F=BA=E7=B1=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 改造 update_dialog.py 为无边框窗口,继承 BaseFramelessDialog
- 增强 BaseFramelessDialog 基类,添加 closeEvent 实现自动断开信号连接
- 清理 edit_palette.py 冗余导入和未使用方法(QDialog、get_dialog_bg_color、_fix_taskbar_icon)
- 简化所有对话框的 closeEvent 方法,改为调用 super().closeEvent(event)
- 删除开发规范中 Windows DWM API 相关内容(3.8.5节)
- 更新无边框对话框基类使用规范,说明基类自动处理信号断开
- 更新核心原则.md 第10节为无边框对话框使用指南
- 更新开发规范版本号至 3.43
---
dialogs/about_dialog.py | 10 +--
dialogs/base_frameless_dialog.py | 15 +++++
dialogs/colorblind_dialog.py | 10 +--
dialogs/contrast_dialog.py | 10 +--
dialogs/edit_palette.py | 31 ++-------
dialogs/export_settings_dialog.py | 10 +--
dialogs/update_dialog.py | 63 +++++++-----------
...00\345\217\221\350\247\204\350\214\203.md" | 64 ++-----------------
...70\345\277\203\345\216\237\345\210\231.md" | 52 ++++++++++-----
9 files changed, 96 insertions(+), 169 deletions(-)
diff --git a/dialogs/about_dialog.py b/dialogs/about_dialog.py
index 641de53..f2db650 100644
--- a/dialogs/about_dialog.py
+++ b/dialogs/about_dialog.py
@@ -348,14 +348,8 @@ class AboutDialog(BaseFramelessDialog):
"""
def closeEvent(self, event):
- """关闭事件 - 断开信号连接"""
- if hasattr(self, '_theme_connection'):
- try:
- qconfig.themeChangedFinished.disconnect(self._theme_connection)
- except (TypeError, RuntimeError):
- pass
- delattr(self, '_theme_connection')
- super().closeEvent(event)
+ """关闭事件"""
+ super().closeEvent(event) # 基类处理信号断开
def contextMenuEvent(self, event):
"""屏蔽原生右键菜单"""
diff --git a/dialogs/base_frameless_dialog.py b/dialogs/base_frameless_dialog.py
index 977eb58..7f17544 100644
--- a/dialogs/base_frameless_dialog.py
+++ b/dialogs/base_frameless_dialog.py
@@ -3,6 +3,7 @@ from PySide6.QtWidgets import QLabel
from PySide6.QtCore import Qt
from PySide6.QtGui import QPixmap, QPalette, QColor
from qframelesswindow import FramelessDialog
+from qfluentwidgets import qconfig
# 项目模块导入
from utils.icon import get_icon_path
@@ -143,3 +144,17 @@ class BaseFramelessDialog(FramelessDialog):
# 更新关闭按钮颜色
self._update_close_button_color(text_color)
+
+ def closeEvent(self, event):
+ """关闭事件:断开主题变化信号连接
+
+ 子类可以重写此方法,但应调用 super().closeEvent(event)
+ 以确保信号正确断开。
+ """
+ if hasattr(self, '_theme_connection'):
+ try:
+ qconfig.themeChangedFinished.disconnect(self._theme_connection)
+ except (TypeError, RuntimeError):
+ pass
+ delattr(self, '_theme_connection')
+ super().closeEvent(event)
diff --git a/dialogs/colorblind_dialog.py b/dialogs/colorblind_dialog.py
index e980269..0aa7c35 100644
--- a/dialogs/colorblind_dialog.py
+++ b/dialogs/colorblind_dialog.py
@@ -322,11 +322,5 @@ class ColorblindPreviewDialog(BaseFramelessDialog):
self.description_label.setText(tr('dialogs.colorblind.description', text=info['description']))
def closeEvent(self, event):
- """关闭事件:断开信号连接"""
- if hasattr(self, '_theme_connection'):
- try:
- qconfig.themeChangedFinished.disconnect(self._theme_connection)
- except (TypeError, RuntimeError):
- pass
- delattr(self, '_theme_connection')
- super().closeEvent(event)
+ """关闭事件"""
+ super().closeEvent(event) # 基类处理信号断开
diff --git a/dialogs/contrast_dialog.py b/dialogs/contrast_dialog.py
index bae3a6b..cf8309a 100644
--- a/dialogs/contrast_dialog.py
+++ b/dialogs/contrast_dialog.py
@@ -585,11 +585,5 @@ class ContrastCheckDialog(BaseFramelessDialog):
self.graphic_preview.set_colors(bg_rgb, text_rgb)
def closeEvent(self, event):
- """关闭事件:断开信号连接"""
- if hasattr(self, '_theme_connection'):
- try:
- qconfig.themeChangedFinished.disconnect(self._theme_connection)
- except (TypeError, RuntimeError):
- pass
- delattr(self, '_theme_connection')
- super().closeEvent(event)
+ """关闭事件"""
+ super().closeEvent(event) # 基类处理信号断开
diff --git a/dialogs/edit_palette.py b/dialogs/edit_palette.py
index 68cf950..8fb45bb 100644
--- a/dialogs/edit_palette.py
+++ b/dialogs/edit_palette.py
@@ -6,7 +6,7 @@ from typing import Dict, Any, List, Tuple, Optional
# 第三方库导入
from PySide6.QtCore import Qt, QTimer, Signal, QPoint, QRect
from PySide6.QtWidgets import (
- QDialog, QHBoxLayout, QLabel, QVBoxLayout, QWidget, QGridLayout, QApplication
+ QHBoxLayout, QLabel, QVBoxLayout, QWidget, QGridLayout, QApplication
)
from PySide6.QtGui import QColor, QPainter, QLinearGradient, QBrush, QPen, QMouseEvent
from qfluentwidgets import (
@@ -21,7 +21,7 @@ from core import (
)
from core.config import get_config_manager
from utils import tr, load_icon_universal
-from utils.theme_colors import get_dialog_bg_color, get_text_color, get_border_color
+from utils.theme_colors import get_text_color, get_border_color
# 对话框模块导入
from .base_frameless_dialog import BaseFramelessDialog
@@ -754,15 +754,7 @@ class ColorInputRow(QWidget):
self._debounce_timer.setSingleShot(True)
self._debounce_timer.timeout.connect(self._process_hex_input)
- def closeEvent(self, event):
- """关闭事件 - 断开信号连接"""
- try:
- if hasattr(self, '_theme_connection'):
- qconfig.themeChangedFinished.disconnect(self._theme_connection)
- delattr(self, '_theme_connection')
- except (TypeError, RuntimeError):
- pass
- super().closeEvent(event)
+
def setup_ui(self):
"""设置界面"""
@@ -972,12 +964,8 @@ class EditPaletteDialog(BaseFramelessDialog):
)
def closeEvent(self, event):
- """关闭事件 - 断开信号连接"""
- try:
- qconfig.themeChangedFinished.disconnect(self._theme_connection)
- except (TypeError, RuntimeError):
- pass
- super().closeEvent(event)
+ """关闭事件"""
+ super().closeEvent(event) # 基类处理信号断开
def setup_ui(self):
"""设置界面布局"""
@@ -1199,11 +1187,4 @@ class EditPaletteDialog(BaseFramelessDialog):
"""
return getattr(self, '_palette_data', None)
- def _fix_taskbar_icon(self):
- """修复任务栏图标"""
- try:
- if self and self.isVisible():
- fix_windows_taskbar_icon_for_window(self)
- except RuntimeError:
- # 对象已被销毁
- pass
+
diff --git a/dialogs/export_settings_dialog.py b/dialogs/export_settings_dialog.py
index ea2c9f4..49c2a36 100644
--- a/dialogs/export_settings_dialog.py
+++ b/dialogs/export_settings_dialog.py
@@ -220,11 +220,5 @@ class ExportSettingsDialog(BaseFramelessDialog):
return "svg"
def closeEvent(self, event):
- """关闭事件:断开信号连接"""
- if hasattr(self, '_theme_connection'):
- try:
- qconfig.themeChangedFinished.disconnect(self._theme_connection)
- except (TypeError, RuntimeError):
- pass
- delattr(self, '_theme_connection')
- super().closeEvent(event)
+ """关闭事件"""
+ super().closeEvent(event) # 基类处理信号断开
diff --git a/dialogs/update_dialog.py b/dialogs/update_dialog.py
index 76e676b..5039b68 100644
--- a/dialogs/update_dialog.py
+++ b/dialogs/update_dialog.py
@@ -3,10 +3,10 @@ import re
from typing import List, Tuple
# 第三方库导入
-from PySide6.QtCore import Qt, QThread, QTimer, QUrl, Signal
+from PySide6.QtCore import Qt, QThread, Signal
from PySide6.QtGui import QDesktopServices
-from PySide6.QtWidgets import QDialog, QHBoxLayout, QLabel, QVBoxLayout, QWidget
-from qfluentwidgets import InfoBar, InfoBarPosition, PrimaryPushButton, PushButton, isDarkTheme, qconfig
+from PySide6.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget
+from qfluentwidgets import InfoBar, InfoBarPosition, PrimaryPushButton, PushButton, qconfig
try:
import requests
@@ -14,8 +14,8 @@ except ImportError:
requests = None
# 项目模块导入
-from utils import tr, fix_windows_taskbar_icon_for_window, load_icon_universal, set_window_title_bar_theme
-from utils.theme_colors import get_dialog_bg_color, get_text_color
+from utils import tr, load_icon_universal
+from dialogs import BaseFramelessDialog
class UpdateCheckThread(QThread):
@@ -134,7 +134,7 @@ def compare_versions(current: str, latest: str) -> int:
return 0
-class UpdateAvailableDialog(QDialog):
+class UpdateAvailableDialog(BaseFramelessDialog):
"""新版本可用提示对话框
当检测到有新版本时弹出,提供跳转到发行页面的功能。
@@ -159,45 +159,33 @@ class UpdateAvailableDialog(QDialog):
# 设置窗口图标
self.setWindowIcon(load_icon_universal())
- # 设置窗口标志
- self.setWindowFlags(
- Qt.WindowType.Window
- | Qt.WindowType.WindowTitleHint
- | Qt.WindowType.WindowCloseButtonHint
- | Qt.WindowType.CustomizeWindowHint
- )
-
- # 设置窗口背景色
- bg_color = get_dialog_bg_color()
- self.setStyleSheet(f"QDialog {{ background-color: {bg_color.name()}; }}")
-
+ # 设置界面
self.setup_ui()
- # 修复任务栏图标
- QTimer.singleShot(100, lambda: fix_windows_taskbar_icon_for_window(self))
+ # 设置标题栏和样式(基类提供)
+ self._setup_title_bar()
+ self._update_styles()
# 监听主题变化
- qconfig.themeChangedFinished.connect(self._update_title_bar_theme)
+ self._theme_connection = qconfig.themeChangedFinished.connect(
+ self._update_styles
+ )
def setup_ui(self):
"""设置界面布局"""
layout = QVBoxLayout(self)
- layout.setContentsMargins(20, 20, 20, 20)
+ # 顶部边距40px为标题栏留出空间
+ layout.setContentsMargins(20, 40, 20, 20)
layout.setSpacing(15)
- # 提示文本
- text_color = get_text_color()
+ # 提示文本(基类统一处理文字颜色)
info_label = QLabel(tr('dialogs.update.new_version'))
info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
- info_label.setStyleSheet(
- f"QLabel {{ color: {text_color.name()}; font-size: 16px; font-weight: bold; }}"
- )
layout.addWidget(info_label)
- # 版本信息
+ # 版本信息(基类统一处理文字颜色)
version_label = QLabel(tr('dialogs.update.version_info', current=self.current_version, latest=self.latest_version))
version_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
- version_label.setStyleSheet(f"QLabel {{ color: {text_color.name()}; font-size: 12px; }}")
layout.addWidget(version_label)
layout.addStretch()
@@ -232,16 +220,9 @@ class UpdateAvailableDialog(QDialog):
QDesktopServices.openUrl(QUrl(url))
self.accept()
- def _update_title_bar_theme(self):
- """更新标题栏主题以适配当前主题"""
- set_window_title_bar_theme(self, isDarkTheme())
-
- def showEvent(self, event):
- """窗口显示事件 - 在显示前设置标题栏主题避免闪烁"""
- # 先设置标题栏主题(在父类 showEvent 之前)
- self._update_title_bar_theme()
- # 调用父类的 showEvent
- super().showEvent(event)
+ def closeEvent(self, event):
+ """关闭事件"""
+ super().closeEvent(event) # 基类处理信号断开
@staticmethod
def check_update(parent, current_version):
@@ -278,7 +259,9 @@ class UpdateAvailableDialog(QDialog):
)
else:
# 有新版本可用,显示对话框
- dialog = UpdateAvailableDialog(parent, current_version, latest_version)
+ # 使用 window() 获取顶层窗口,确保无边框对话框正常显示
+ top_parent = parent.window() if parent else None
+ dialog = UpdateAvailableDialog(top_parent, current_version, latest_version)
dialog.exec()
else:
InfoBar.warning(
diff --git "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md" "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md"
index cbc82e8..dd09dc7 100644
--- "a/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md"
+++ "b/\346\226\207\346\241\243/\345\274\200\345\217\221\350\247\204\350\214\203.md"
@@ -368,51 +368,6 @@ class MyWidget(QWidget):
**避免使用** `!important`:优先使用组件特定的选择器;如必须使用,确保在主题切换后重新应用。
-#### 3.8.5 Windows 原生标题栏深色模式
-
-对于继承自 `QDialog` 的对话框,使用 Windows DWM API 设置原生标题栏的沉浸式深色模式。
-
-**工具函数**(放在 `utils/platform.py`):
-
-```python
-import ctypes
-import sys
-
-DWMWA_USE_IMMERSIVE_DARK_MODE = 20
-
-def set_window_title_bar_theme(window, is_dark=False):
- """为窗口设置标题栏主题(Windows 10+)"""
- if sys.platform != "win32":
- return False
- try:
- hwnd = int(window.windowHandle().winId())
- value = ctypes.c_int(1 if is_dark else 0)
- return ctypes.windll.dwmapi.DwmSetWindowAttribute(
- hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE,
- ctypes.byref(value), ctypes.sizeof(value)
- ) == 0
- except Exception:
- return False
-```
-
-**在对话框中应用**(使用 `showEvent` 避免闪烁):
-
-```python
-class MyDialog(QDialog):
- def __init__(self, parent=None):
- super().__init__(parent)
- qconfig.themeChangedFinished.connect(self._update_title_bar_theme)
-
- def showEvent(self, event):
- self._update_title_bar_theme()
- super().showEvent(event)
-
- def _update_title_bar_theme(self):
- set_window_title_bar_theme(self, isDarkTheme())
-```
-
-**关键要点:** 使用 `showEvent` 在窗口显示前设置标题栏主题;连接 `qconfig.themeChangedFinished` 信号支持已打开对话框的主题切换;仅支持 Windows 10 版本 2004 及以上。
-
***
## 4. 基类设计规范
@@ -534,15 +489,11 @@ self._theme_connection = qconfig.themeChangedFinished.connect(
)
```
-4. **断开信号连接**
+4. **关闭事件处理(基类已自动处理信号断开)**
```python
def closeEvent(self, event):
- """关闭事件 - 断开信号连接"""
- try:
- qconfig.themeChangedFinished.disconnect(self._theme_connection)
- except (TypeError, RuntimeError):
- pass
- super().closeEvent(event)
+ """关闭事件"""
+ super().closeEvent(event) # 基类自动断开信号连接
```
**完整示例:**
@@ -569,11 +520,8 @@ class CustomDialog(BaseFramelessDialog):
)
def closeEvent(self, event):
- try:
- qconfig.themeChangedFinished.disconnect(self._theme_connection)
- except (TypeError, RuntimeError):
- pass
- super().closeEvent(event)
+ """关闭事件"""
+ super().closeEvent(event) # 基类自动断开信号连接
def setup_ui(self):
"""设置界面布局"""
@@ -589,6 +537,7 @@ class CustomDialog(BaseFramelessDialog):
- 必须调用 `_update_styles()` 初始化样式
- 布局边距顶部设置为 40px,为标题栏留出空间
- 父窗口必须是顶层窗口:`parent=self.window()`
+- `closeEvent` 中调用 `super().closeEvent(event)`,基类会自动断开主题变化信号
### 5.2 界面组织规范
@@ -2057,6 +2006,7 @@ def _get_about_text(self):
| 版本 | 日期 | 变更内容 |
| :--: | :--------: | :------------------------------------------------------------------------: |
+| 3.43 | 2026-03-16 | 更新无边框对话框基类规范(5.1.1节):`closeEvent` 信号断开逻辑移至基类;删除 Windows DWM API 相关内容(3.8.5节) |
| 3.42 | 2026-03-16 | 新增无边框对话框基类规范(5.1.1节):提取 BaseFramelessDialog 到独立文件,统一标题栏和主题适配;更新项目结构(1.3节) |
| 3.41 | 2026-03-13 | 精简开发规范中的代码示例,删除冗余注释和重复内容,使文档更加简洁易读 |
| 3.40 | 2026-03-07 | 更新项目结构(1.3节):新增 export\_settings\_dialog.py 到 dialogs/ 目录 |
diff --git "a/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" "b/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md"
index 3696230..d9b25ec 100644
--- "a/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md"
+++ "b/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md"
@@ -268,25 +268,47 @@ class MyInterface(QWidget):
---
-## 10. Windows 原生标题栏深色模式
+## 10. 无边框对话框
-对于继承自 `QDialog` 的对话框,使用 Windows DWM API 设置原生标题栏主题:
+使用 `BaseFramelessDialog` 作为对话框基类,提供 Fluent Design 风格的自定义标题栏:
```python
-from qfluentwidgets import isDarkTheme, qconfig
-from utils import set_window_title_bar_theme
+from dialogs import BaseFramelessDialog
+from qfluentwidgets import qconfig
-class MyDialog(QDialog):
+class MyDialog(BaseFramelessDialog):
def __init__(self, parent=None):
super().__init__(parent)
- qconfig.themeChangedFinished.connect(self._update_title_bar_theme)
-
- def showEvent(self, event):
- """窗口显示前设置标题栏主题,避免闪烁"""
- self._update_title_bar_theme()
- super().showEvent(event)
-
- def _update_title_bar_theme(self):
- """更新标题栏主题"""
- set_window_title_bar_theme(self, isDarkTheme())
+ self.setWindowTitle("对话框标题")
+ self.setFixedSize(400, 300)
+
+ # 设置标题栏和样式
+ self._setup_title_bar()
+ self._update_styles()
+
+ # 设置界面
+ self.setup_ui()
+
+ # 监听主题变化
+ self._theme_connection = qconfig.themeChangedFinished.connect(
+ self._update_styles
+ )
+
+ def closeEvent(self, event):
+ """关闭事件"""
+ super().closeEvent(event) # 基类自动断开信号连接
+
+ def setup_ui(self):
+ """设置界面布局"""
+ from PySide6.QtWidgets import QVBoxLayout
+ layout = QVBoxLayout(self)
+ # 顶部边距40px为标题栏留出空间
+ layout.setContentsMargins(20, 40, 20, 20)
```
+
+**关键要点:**
+- 继承 `BaseFramelessDialog` 而非 `QDialog`
+- 调用 `_setup_title_bar()` 设置自定义标题栏
+- 调用 `_update_styles()` 初始化样式
+- 布局边距顶部设置为 40px,为标题栏留出空间
+- `closeEvent` 中调用 `super().closeEvent(event)`,基类会自动断开主题变化信号
--
Gitee
From df59fd3487791f3c64c4c9a44c1f8f7470d58081 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?=
Date: Mon, 16 Mar 2026 22:46:12 +0800
Subject: [PATCH 07/47] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E5=AF=B9=E6=AF=94=E5=BA=A6=E6=A3=80=E6=9F=A5=E5=AF=B9=E8=AF=9D?=
=?UTF-8?q?=E6=A1=86=E7=A7=BB=E5=8A=A8=E5=90=8E=E9=AB=98=E5=BA=A6=E7=BC=A9?=
=?UTF-8?q?=E5=B0=8F=E5=92=8C=E5=86=85=E5=AE=B9=E9=87=8D=E5=8F=A0=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复窗口大小设置:将 setMinimumSize/setMaximumSize/resize 合并为 setFixedSize
- 调整窗口高度:从 420px 增加到 580px,确保所有控件正常显示不重叠
- 优化窗口尺寸计算:根据各控件实际高度需求重新计算总高度
---
dialogs/contrast_dialog.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/dialogs/contrast_dialog.py b/dialogs/contrast_dialog.py
index cf8309a..2032590 100644
--- a/dialogs/contrast_dialog.py
+++ b/dialogs/contrast_dialog.py
@@ -374,9 +374,7 @@ class ContrastCheckDialog(BaseFramelessDialog):
self.setWindowIcon(load_icon_universal())
# 设置固定大小
- self.setMinimumSize(480, 420)
- self.setMaximumSize(480, 420)
- self.resize(480, 420)
+ self.setFixedSize(480, 580)
# 设置界面
self.setup_ui()
--
Gitee
From 7caaf17a5cb10c14b7447d645b5f480936507739 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?=
Date: Mon, 16 Mar 2026 23:13:43 +0800
Subject: [PATCH 08/47] =?UTF-8?q?[=E6=96=87=E6=A1=A3]=20=E5=9C=A8=E6=A0=B8?=
=?UTF-8?q?=E5=BF=83=E5=8E=9F=E5=88=99=E4=B8=AD=E6=B7=BB=E5=8A=A0=E6=97=A5?=
=?UTF-8?q?=E5=BF=97=E7=B3=BB=E7=BB=9F=E8=AF=B4=E6=98=8E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在文档/核心原则.md 中新增第10章"日志系统说明"
- 说明日志存储位置为用户主目录,非项目目录
- 添加AI提示,避免沙箱环境问题导致过度修改
---
...40\270\345\277\203\345\216\237\345\210\231.md" | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git "a/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md" "b/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md"
index d9b25ec..84478bd 100644
--- "a/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md"
+++ "b/\346\226\207\346\241\243/\346\240\270\345\277\203\345\216\237\345\210\231.md"
@@ -268,7 +268,20 @@ class MyInterface(QWidget):
---
-## 10. 无边框对话框
+## 10. 日志系统说明
+
+日志存储在用户主目录 `~/.color_card/logs/`,**不是项目目录**。
+
+```python
+# 日志目录自动创建,已存在不会报错
+self._log_dir.mkdir(parents=True, exist_ok=True)
+```
+
+> **AI 提示**:沙箱环境无法访问日志是环境限制,不是代码问题。不要修改日志系统,不用手动创建目录。
+
+---
+
+## 12. 无边框对话框
使用 `BaseFramelessDialog` 作为对话框基类,提供 Fluent Design 风格的自定义标题栏:
--
Gitee
From 27c2b1cc8684b17071eba70c11118bc8d686c938 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?=
Date: Mon, 16 Mar 2026 23:24:56 +0800
Subject: [PATCH 09/47] =?UTF-8?q?[=E5=86=85=E5=AE=B9=E8=B0=83=E6=95=B4]=20?=
=?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC=E4=BF=A1=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
version.py | 4 ++--
version.txt | 6 +++---
version_info.txt | 8 ++++----
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/version.py b/version.py
index a22ae18..416913f 100644
--- a/version.py
+++ b/version.py
@@ -9,9 +9,9 @@ class VersionManager:
# 版本号组件
self.major: int = 1
self.minor: int = 5
- self.patch: int = 1
+ self.patch: int = 2
self.build: int = 0
- self.prerelease: str = ""
+ self.prerelease: str = "Beta"
# 核心版本信息
self.version: str = f"{self.major}.{self.minor}.{self.patch}{self.prerelease}"
diff --git a/version.txt b/version.txt
index ab7dd43..3f3a13a 100644
--- a/version.txt
+++ b/version.txt
@@ -1,6 +1,6 @@
-1.5.1
-2026.3.15.1
-1.5.1.0
+1.5.2
+2026.3.16.1
+1.5.2.0
浮晓 HXiao Studio
© 2026 浮晓 HXiao Studio
取色卡 - Color Card
\ No newline at end of file
diff --git a/version_info.txt b/version_info.txt
index 691c4f1..1e6a277 100644
--- a/version_info.txt
+++ b/version_info.txt
@@ -1,7 +1,7 @@
VSVersionInfo(
ffi=FixedFileInfo(
- filevers=(2026,3,15,1),
- prodvers=(1,5,1,0),
+ filevers=(2026,3,16,1),
+ prodvers=(1,5,2,0),
mask=0x3f,
flags=0x0,
OS=0x4,
@@ -17,12 +17,12 @@ VSVersionInfo(
[
StringStruct(u'CompanyName', u'浮晓 HXiao Studio'),
StringStruct(u'FileDescription', u'取色卡 - Color Card'),
- StringStruct(u'FileVersion', u'1.5.1'),
+ StringStruct(u'FileVersion', u'1.5.2'),
StringStruct(u'InternalName', u'Color_Card'),
StringStruct(u'LegalCopyright', u'© 2026 浮晓 HXiao Studio'),
StringStruct(u'OriginalFilename', u'Color_Card.exe'),
StringStruct(u'ProductName', u'取色卡'),
- StringStruct(u'ProductVersion', u'1.5.1'),
+ StringStruct(u'ProductVersion', u'1.5.2'),
StringStruct(u'Comments', u'一站式的图片的图片分析和配色工具')
]
)
--
Gitee
From 98f07303bfda8d324ce4e3ebf8f2d632a0fba929 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?=
Date: Tue, 17 Mar 2026 01:35:56 +0800
Subject: [PATCH 10/47] =?UTF-8?q?[=E5=86=85=E5=AE=B9=E8=B0=83=E6=95=B4]=20?=
=?UTF-8?q?=E4=BF=AE=E6=AD=A3=20PySideSix-Frameless-Window=20=E8=AE=B8?=
=?UTF-8?q?=E5=8F=AF=E8=AF=81=E4=BF=A1=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修正 about_dialog.py 中 PySideSix-Frameless-Window 的许可证为 LGPLv3
- 修正 LICENSE 文件中 PySideSix-Frameless-Window 的许可证为 LGPLv3
- 修正 LICENSE.html 中 PySideSix-Frameless-Window 的许可证为 LGPLv3
---
LICENSE | 8 ++++----
dialogs/about_dialog.py | 2 +-
file/LICENSE.html | 4 ++--
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/LICENSE b/LICENSE
index 2b8b5ef..9a4d027 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1032,12 +1032,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
版权所有:zhiyiYo
项目地址:https://github.com/zhiyiYo/PyQt-Frameless-Window
-许可证:GNU General Public License v3.0
+许可证:GNU Lesser General Public License v3.0
说明:
-PySideSix-Frameless-Window 使用 GPLv3 许可证。由于本项目主许可证也为 GPLv3,
-完整的 GPLv3 许可证文本请参考本文档前面的 "GNU GENERAL PUBLIC LICENSE
-Version 3" 章节。
+PySideSix-Frameless-Window 使用 LGPLv3 许可证。由于本项目主许可证为 GPLv3,
+而 LGPLv3 是 GPLv3 的补充版本,完整的 LGPLv3 许可证文本请参考本文档
+"PySide6" 章节中的 "GNU LESSER GENERAL PUBLIC LICENSE Version 3" 部分。
================================================================================
diff --git a/dialogs/about_dialog.py b/dialogs/about_dialog.py
index f2db650..e859148 100644
--- a/dialogs/about_dialog.py
+++ b/dialogs/about_dialog.py
@@ -237,7 +237,7 @@ class AboutDialog(BaseFramelessDialog):
• 本程序使用 PySideSix-Frameless-Window 实现对话框无边框窗口
版权所有:zhiyiYo
- 许可证:GPLv3
+ 许可证:LGPLv3
项目地址:https://github.com/zhiyiYo/PyQt-Frameless-Window
【开源配色方案使用说明】
diff --git a/file/LICENSE.html b/file/LICENSE.html
index ca60aba..82bd779 100644
--- a/file/LICENSE.html
+++ b/file/LICENSE.html
@@ -695,11 +695,11 @@
-
PySideSix-Frameless-Window 使用 GPLv3 许可证。由于本项目主许可证也为 GPLv3,完整的 GPLv3 许可证文本请参考本文档前面的GNU GENERAL PUBLIC LICENSE Version 3章节。
+
PySideSix-Frameless-Window 使用 LGPLv3 许可证。由于本项目主许可证为 GPLv3,而 LGPLv3 是 GPLv3 的补充版本,完整的 LGPLv3 许可证文本请参考本文档 PySide6 章节中的 "GNU LESSER GENERAL PUBLIC LICENSE Version 3" 部分。
--
Gitee
From 6b12abc21c041d72008ffbe508386d4eb614bb2b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=9D=92=E5=B1=B1=E5=85=AC=E4=BB=94?=