摘要
在Python GUI开发领域,Tkinter/ttk组合长期被误解为"过时"和"简陋"的代名词。本文旨在彻底改变这一认知,通过系统性的布局设计方法、现代化的样式系统、可视化设计工具生态以及四个实战工程,展示如何基于Python标准库打造出既美观又实用的现代化界面。文章将深入探讨Tkinter/ttk的布局设计哲学、响应式策略、主题定制技巧,并提供可直接复用的代码模板,帮助开发者在不依赖外部依赖的情况下,构建出媲美PyQt6/PySide6视觉体验的应用程序。
=================================================
第三部分:现代化样式系统
=================================================
3.1 ttk.Style机制深度解析
ttk(Themed Tkinter)是Tkinter的现代化主题版本,它通过分离样式和逻辑,提供了更加美观和一致的界面。ttk.Style是控制ttk组件外观的核心机制。
3.1.1 ttk.Style架构设计
Mermaid图表:ttk.Style系统架构

3.1.2 ttk.Style核心概念
元素(Element)
元素是ttk样式系统的基本构建块,每个元素代表组件的一个视觉部分。例如,一个按钮可能包含以下元素:
-
border:边框元素 -
focus:焦点指示器 -
padding:内边距 -
label:文本标签
样式(Style)
样式是元素的集合,定义了组件的外观。每个样式都有一个名称,如 TButton、TEntry等。
主题(Theme)
主题是一组相关样式的集合,定义了整个应用程序的视觉风格。ttk内置了多个主题,也可以通过第三方库添加新主题。
状态映射(State Map)
状态映射定义了组件在不同状态下的外观变化。常见的状态包括:
-
active:鼠标悬停 -
disabled:禁用状态 -
focus:获得焦点 -
pressed:按下状态 -
selected:选中状态
3.1.3 实战:创建现代化主题系统
python
"""
现代化主题系统实现
演示如何创建和管理完整的主题系统
"""
import tkinter as tk
from tkinter import ttk
import json
from pathlib import Path
from typing import Dict, Any, Optional, List
import logging
class ThemeManager:
"""主题管理器"""
def __init__(self):
self.styles: Dict[str, ttk.Style] = {}
self.current_theme: str = "light"
self.themes: Dict[str, Dict[str, Any]] = {}
self.custom_styles: Dict[str, Dict[str, Any]] = {}
# 初始化内置主题
self._init_builtin_themes()
# 设置日志
self.logger = self._setup_logger()
def _setup_logger(self):
"""设置日志"""
logger = logging.getLogger("ThemeManager")
logger.setLevel(logging.INFO)
if not logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
def _init_builtin_themes(self):
"""初始化内置主题"""
# 浅色主题
self.themes["light"] = {
"name": "浅色主题",
"type": "light",
"colors": {
"primary": "#007acc",
"secondary": "#6c757d",
"success": "#28a745",
"danger": "#dc3545",
"warning": "#ffc107",
"info": "#17a2b8",
"background": "#ffffff",
"foreground": "#212529",
"border": "#dee2e6",
"input_bg": "#ffffff",
"input_fg": "#212529",
"select_bg": "#007acc",
"select_fg": "#ffffff"
},
"fonts": {
"default": ("Segoe UI", 10),
"heading": ("Segoe UI", 12, "bold"),
"title": ("Segoe UI", 14, "bold"),
"monospace": ("Consolas", 10)
},
"spacing": {
"small": 2,
"medium": 5,
"large": 10,
"xlarge": 20
},
"borders": {
"width": 1,
"radius": 4
}
}
# 深色主题
self.themes["dark"] = {
"name": "深色主题",
"type": "dark",
"colors": {
"primary": "#0d6efd",
"secondary": "#6c757d",
"success": "#198754",
"danger": "#dc3545",
"warning": "#ffc107",
"info": "#0dcaf0",
"background": "#212529",
"foreground": "#f8f9fa",
"border": "#495057",
"input_bg": "#343a40",
"input_fg": "#f8f9fa",
"select_bg": "#0d6efd",
"select_fg": "#ffffff"
},
"fonts": {
"default": ("Segoe UI", 10),
"heading": ("Segoe UI", 12, "bold"),
"title": ("Segoe UI", 14, "bold"),
"monospace": ("Consolas", 10)
},
"spacing": {
"small": 2,
"medium": 5,
"large": 10,
"xlarge": 20
},
"borders": {
"width": 1,
"radius": 4
}
}
# 蓝色主题
self.themes["blue"] = {
"name": "蓝色主题",
"type": "light",
"colors": {
"primary": "#0d6efd",
"secondary": "#6c757d",
"success": "#198754",
"danger": "#dc3545",
"warning": "#ffc107",
"info": "#0dcaf0",
"background": "#f8f9fa",
"foreground": "#212529",
"border": "#dee2e6",
"input_bg": "#ffffff",
"input_fg": "#212529",
"select_bg": "#0d6efd",
"select_fg": "#ffffff"
},
"fonts": {
"default": ("Segoe UI", 10),
"heading": ("Segoe UI", 12, "bold"),
"title": ("Segoe UI", 14, "bold"),
"monospace": ("Consolas", 10)
},
"spacing": {
"small": 2,
"medium": 5,
"large": 10,
"xlarge": 20
},
"borders": {
"width": 1,
"radius": 4
}
}
def create_style(self, root: tk.Tk) -> ttk.Style:
"""创建样式对象"""
style = ttk.Style(root)
self.styles[root.winfo_id()] = style
# 设置默认主题
self.apply_theme(root, self.current_theme)
return style
def apply_theme(self, root: tk.Tk, theme_name: str):
"""应用主题"""
if theme_name not in self.themes:
self.logger.warning(f"主题 '{theme_name}' 不存在,使用默认主题")
theme_name = "light"
theme = self.themes[theme_name]
self.current_theme = theme_name
# 获取或创建样式对象
if root.winfo_id() not in self.styles:
self.create_style(root)
style = self.styles[root.winfo_id()]
# 设置主题
try:
# 尝试设置内置主题
style.theme_use("clam") # 使用clam作为基础主题
# 应用自定义样式
self._apply_custom_styles(style, theme)
# 记录主题切换
self.logger.info(f"已应用主题: {theme['name']}")
return True
except Exception as e:
self.logger.error(f"应用主题失败: {e}")
return False
def _apply_custom_styles(self, style: ttk.Style, theme: Dict[str, Any]):
"""应用自定义样式"""
colors = theme["colors"]
# 配置按钮样式
self._configure_button_styles(style, colors)
# 配置标签样式
self._configure_label_styles(style, colors, theme["fonts"])
# 配置输入框样式
self._configure_entry_styles(style, colors)
# 配置组合框样式
self._configure_combobox_styles(style, colors)
# 配置进度条样式
self._configure_progressbar_styles(style, colors)
# 配置树形视图样式
self._configure_treeview_styles(style, colors)
# 配置滚动条样式
self._configure_scrollbar_styles(style, colors)
# 配置分隔线样式
self._configure_separator_styles(style, colors)
# 配置笔记本样式
self._configure_notebook_styles(style, colors)
def _configure_button_styles(self, style: ttk.Style, colors: Dict[str, str]):
"""配置按钮样式"""
# 默认按钮
style.configure(
"TButton",
background=colors["primary"],
foreground=colors["select_fg"],
borderwidth=1,
focusthickness=3,
focuscolor=colors["primary"],
padding=6,
font=("Segoe UI", 10)
)
style.map(
"TButton",
background=[
("active", self._adjust_color(colors["primary"], -20)),
("disabled", colors["secondary"])
],
foreground=[
("disabled", colors["secondary"])
]
)
# 主要按钮
style.configure(
"Primary.TButton",
background=colors["primary"],
foreground=colors["select_fg"]
)
# 成功按钮
style.configure(
"Success.TButton",
background=colors["success"],
foreground=colors["select_fg"]
)
# 危险按钮
style.configure(
"Danger.TButton",
background=colors["danger"],
foreground=colors["select_fg"]
)
# 警告按钮
style.configure(
"Warning.TButton",
background=colors["warning"],
foreground=colors["foreground"]
)
# 信息按钮
style.configure(
"Info.TButton",
background=colors["info"],
foreground=colors["select_fg"]
)
# 次要按钮
style.configure(
"Secondary.TButton",
background=colors["secondary"],
foreground=colors["select_fg"]
)
def _configure_label_styles(self, style: ttk.Style, colors: Dict[str, str], fonts: Dict[str, Any]):
"""配置标签样式"""
# 默认标签
style.configure(
"TLabel",
background=colors["background"],
foreground=colors["foreground"],
font=fonts["default"]
)
# 标题标签
style.configure(
"Title.TLabel",
font=fonts["title"],
foreground=colors["primary"]
)
# 副标题标签
style.configure(
"Subtitle.TLabel",
font=fonts["heading"],
foreground=colors["secondary"]
)
# 成功标签
style.configure(
"Success.TLabel",
foreground=colors["success"]
)
# 危险标签
style.configure(
"Danger.TLabel",
foreground=colors["danger"]
)
# 警告标签
style.configure(
"Warning.TLabel",
foreground=colors["warning"]
)
# 信息标签
style.configure(
"Info.TLabel",
foreground=colors["info"]
)
def _configure_entry_styles(self, style: ttk.Style, colors: Dict[str, str]):
"""配置输入框样式"""
style.configure(
"TEntry",
fieldbackground=colors["input_bg"],
foreground=colors["input_fg"],
borderwidth=1,
focusthickness=1,
focuscolor=colors["primary"],
padding=5
)
style.map(
"TEntry",
fieldbackground=[
("readonly", colors["background"]),
("disabled", colors["background"])
],
foreground=[
("disabled", colors["secondary"])
]
)
# 成功输入框
style.configure(
"Success.TEntry",
fieldbackground=self._adjust_color(colors["success"], 90),
foreground=colors["input_fg"]
)
# 错误输入框
style.configure(
"Error.TEntry",
fieldbackground=self._adjust_color(colors["danger"], 90),
foreground=colors["input_fg"]
)
def _configure_combobox_styles(self, style: ttk.Style, colors: Dict[str, str]):
"""配置组合框样式"""
style.configure(
"TCombobox",
fieldbackground=colors["input_bg"],
foreground=colors["input_fg"],
background=colors["input_bg"],
borderwidth=1,
arrowsize=12,
padding=5
)
style.map(
"TCombobox",
fieldbackground=[
("readonly", colors["background"]),
("disabled", colors["background"])
],
foreground=[
("disabled", colors["secondary"])
],
background=[
("disabled", colors["background"])
]
)
def _configure_progressbar_styles(self, style: ttk.Style, colors: Dict[str, str]):
"""配置进度条样式"""
# 水平进度条
style.configure(
"Horizontal.TProgressbar",
background=colors["primary"],
troughcolor=colors["border"],
bordercolor=colors["border"],
lightcolor=colors["primary"],
darkcolor=colors["primary"],
thickness=20
)
# 垂直进度条
style.configure(
"Vertical.TProgressbar",
background=colors["primary"],
troughcolor=colors["border"],
bordercolor=colors["border"],
lightcolor=colors["primary"],
darkcolor=colors["primary"]
)
# 成功进度条
style.configure(
"Success.Horizontal.TProgressbar",
background=colors["success"]
)
# 危险进度条
style.configure(
"Danger.Horizontal.TProgressbar",
background=colors["danger"]
)
# 警告进度条
style.configure(
"Warning.Horizontal.TProgressbar",
background=colors["warning"]
)
def _configure_treeview_styles(self, style: ttk.Style, colors: Dict[str, str]):
"""配置树形视图样式"""
style.configure(
"Treeview",
background=colors["background"],
foreground=colors["foreground"],
fieldbackground=colors["background"],
borderwidth=0,
rowheight=25
)
style.map(
"Treeview",
background=[("selected", colors["select_bg"])],
foreground=[("selected", colors["select_fg"])]
)
# 树形视图标题
style.configure(
"Treeview.Heading",
background=colors["border"],
foreground=colors["foreground"],
borderwidth=1,
relief="solid"
)
style.map(
"Treeview.Heading",
background=[("active", colors["primary"])]
)
def _configure_scrollbar_styles(self, style: ttk.Style, colors: Dict[str, str]):
"""配置滚动条样式"""
style.configure(
"TScrollbar",
background=colors["border"],
troughcolor=colors["background"],
bordercolor=colors["background"],
arrowsize=12,
width=12
)
style.map(
"TScrollbar",
background=[("active", colors["primary"])]
)
def _configure_separator_styles(self, style: ttk.Style, colors: Dict[str, str]):
"""配置分隔线样式"""
style.configure(
"TSeparator",
background=colors["border"]
)
def _configure_notebook_styles(self, style: ttk.Style, colors: Dict[str, str]):
"""配置笔记本样式"""
style.configure(
"TNotebook",
background=colors["background"],
borderwidth=1
)
style.configure(
"TNotebook.Tab",
background=colors["border"],
foreground=colors["foreground"],
padding=[10, 5],
borderwidth=1
)
style.map(
"TNotebook.Tab",
background=[("selected", colors["background"])],
foreground=[("selected", colors["primary"])],
expand=[("selected", [1, 1, 1, 0])]
)
def _adjust_color(self, color: str, amount: int) -> str:
"""调整颜色亮度"""
# 简化实现:在实际应用中应该使用更复杂的颜色调整算法
# 这里返回原色
return color
def register_custom_style(self, style_name: str, style_def: Dict[str, Any]):
"""注册自定义样式"""
self.custom_styles[style_name] = style_def
self.logger.info(f"已注册自定义样式: {style_name}")
def get_theme_names(self) -> List[str]:
"""获取所有主题名称"""
return list(self.themes.keys())
def get_theme_info(self, theme_name: str) -> Optional[Dict[str, Any]]:
"""获取主题信息"""
return self.themes.get(theme_name)
def add_theme(self, theme_name: str, theme_def: Dict[str, Any]):
"""添加新主题"""
if theme_name in self.themes:
self.logger.warning(f"主题 '{theme_name}' 已存在,将被覆盖")
self.themes[theme_name] = theme_def
self.logger.info(f"已添加主题: {theme_name}")
def remove_theme(self, theme_name: str) -> bool:
"""移除主题"""
if theme_name in self.themes and theme_name not in ["light", "dark"]:
del self.themes[theme_name]
self.logger.info(f"已移除主题: {theme_name}")
return True
return False
def save_theme(self, theme_name: str, filepath: str):
"""保存主题到文件"""
if theme_name not in self.themes:
self.logger.error(f"主题 '{theme_name}' 不存在")
return False
try:
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(self.themes[theme_name], f, indent=2, ensure_ascii=False)
self.logger.info(f"主题 '{theme_name}' 已保存到: {filepath}")
return True
except Exception as e:
self.logger.error(f"保存主题失败: {e}")
return False
def load_theme(self, theme_name: str, filepath: str) -> bool:
"""从文件加载主题"""
try:
with open(filepath, 'r', encoding='utf-8') as f:
theme_def = json.load(f)
self.themes[theme_name] = theme_def
self.logger.info(f"已从文件加载主题: {theme_name}")
return True
except Exception as e:
self.logger.error(f"加载主题失败: {e}")
return False
class ThemeDemoApplication:
"""主题演示应用程序"""
def __init__(self):
# 创建主窗口
self.root = tk.Tk()
self.root.title("现代化主题系统演示")
self.root.geometry("1000x700")
# 初始化主题管理器
self.theme_manager = ThemeManager()
self.style = self.theme_manager.create_style(self.root)
# 设置窗口图标和初始主题
self._setup_window()
# 创建界面
self.create_ui()
# 应用初始主题
self.apply_theme("light")
def _setup_window(self):
"""设置窗口属性"""
# 设置窗口图标(如果有)
try:
self.root.iconbitmap("icon.ico")
except:
pass
# 设置窗口最小大小
self.root.minsize(800, 600)
# 绑定窗口关闭事件
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
def create_ui(self):
"""创建用户界面"""
# 创建主容器
main_container = ttk.Frame(self.root)
main_container.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 配置网格权重
main_container.columnconfigure(0, weight=1)
main_container.rowconfigure(1, weight=1)
# 1. 顶部工具栏
self.create_toolbar(main_container)
# 2. 主内容区域
self.create_content_area(main_container)
# 3. 底部状态栏
self.create_statusbar(main_container)
def create_toolbar(self, parent):
"""创建工具栏"""
toolbar = ttk.Frame(parent, relief=tk.RAISED, borderwidth=1)
toolbar.grid(row=0, column=0, sticky="ew", pady=(0, 10))
# 左侧:主题选择
left_frame = ttk.Frame(toolbar)
left_frame.pack(side=tk.LEFT, padx=10, pady=5)
ttk.Label(left_frame, text="主题:").pack(side=tk.LEFT, padx=(0, 5))
self.theme_var = tk.StringVar(value="light")
theme_combo = ttk.Combobox(
left_frame,
textvariable=self.theme_var,
values=self.theme_manager.get_theme_names(),
state="readonly",
width=15
)
theme_combo.pack(side=tk.LEFT)
theme_combo.bind("<<ComboboxSelected>>", self.on_theme_change)
# 右侧:按钮区域
right_frame = ttk.Frame(toolbar)
right_frame.pack(side=tk.RIGHT, padx=10, pady=5)
ttk.Button(
right_frame,
text="刷新",
command=self.refresh_ui
).pack(side=tk.LEFT, padx=2)
ttk.Button(
right_frame,
text="关于",
command=self.show_about
).pack(side=tk.LEFT, padx=2)
ttk.Button(
right_frame,
text="退出",
command=self.root.quit
).pack(side=tk.LEFT, padx=2)
def create_content_area(self, parent):
"""创建主内容区域"""
# 创建笔记本控件
self.notebook = ttk.Notebook(parent)
self.notebook.grid(row=1, column=0, sticky="nsew")
# 标签1:控件展示
self.create_widgets_tab()
# 标签2:主题配置
self.create_theme_config_tab()
# 标签3:样式预览
self.create_preview_tab()
def create_widgets_tab(self):
"""创建控件展示标签页"""
tab1 = ttk.Frame(self.notebook)
self.notebook.add(tab1, text="控件展示")
# 使用网格布局
tab1.columnconfigure(0, weight=1)
tab1.columnconfigure(1, weight=1)
tab1.columnconfigure(2, weight=1)
# 行0:标签展示
label_frame = ttk.LabelFrame(tab1, text="标签样式", padding=10)
label_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
ttk.Label(label_frame, text="默认标签").pack(pady=2)
ttk.Label(label_frame, text="主要标签", style="Title.TLabel").pack(pady=2)
ttk.Label(label_frame, text="成功标签", style="Success.TLabel").pack(pady=2)
ttk.Label(label_frame, text="危险标签", style="Danger.TLabel").pack(pady=2)
ttk.Label(label_frame, text="警告标签", style="Warning.TLabel").pack(pady=2)
ttk.Label(label_frame, text="信息标签", style="Info.TLabel").pack(pady=2)
# 行0:按钮展示
button_frame = ttk.LabelFrame(tab1, text="按钮样式", padding=10)
button_frame.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
ttk.Button(button_frame, text="默认按钮").pack(pady=2, fill=tk.X)
ttk.Button(button_frame, text="主要按钮", style="Primary.TButton").pack(pady=2, fill=tk.X)
ttk.Button(button_frame, text="成功按钮", style="Success.TButton").pack(pady=2, fill=tk.X)
ttk.Button(button_frame, text="危险按钮", style="Danger.TButton").pack(pady=2, fill=tk.X)
ttk.Button(button_frame, text="警告按钮", style="Warning.TButton").pack(pady=2, fill=tk.X)
ttk.Button(button_frame, text="信息按钮", style="Info.TButton").pack(pady=2, fill=tk.X)
ttk.Button(button_frame, text="禁用按钮", state=tk.DISABLED).pack(pady=2, fill=tk.X)
# 行0:输入控件展示
input_frame = ttk.LabelFrame(tab1, text="输入控件", padding=10)
input_frame.grid(row=0, column=2, padx=5, pady=5, sticky="nsew")
ttk.Label(input_frame, text="文本框:").pack(anchor=tk.W, pady=(0, 2))
ttk.Entry(input_frame).pack(fill=tk.X, pady=(0, 10))
ttk.Label(input_frame, text="密码框:").pack(anchor=tk.W, pady=(0, 2))
ttk.Entry(input_frame, show="*").pack(fill=tk.X, pady=(0, 10))
ttk.Label(input_frame, text="组合框:").pack(anchor=tk.W, pady=(0, 2))
ttk.Combobox(input_frame, values=["选项1", "选项2", "选项3"]).pack(fill=tk.X)
# 行1:进度条和滚动条
progress_frame = ttk.LabelFrame(tab1, text="进度条", padding=10)
progress_frame.grid(row=1, column=0, padx=5, pady=5, sticky="nsew", columnspan=2)
ttk.Label(progress_frame, text="默认进度条:").pack(anchor=tk.W)
ttk.Progressbar(progress_frame, mode="determinate", value=50).pack(fill=tk.X, pady=(0, 10))
ttk.Label(progress_frame, text="成功进度条:").pack(anchor=tk.W)
ttk.Progressbar(progress_frame, mode="determinate", value=75, style="Success.Horizontal.TProgressbar").pack(fill=tk.X, pady=(0, 10))
ttk.Label(progress_frame, text="危险进度条:").pack(anchor=tk.W)
ttk.Progressbar(progress_frame, mode="determinate", value=25, style="Danger.Horizontal.TProgressbar").pack(fill=tk.X)
# 行1:滚动条演示
scroll_frame = ttk.LabelFrame(tab1, text="滚动区域", padding=10)
scroll_frame.grid(row=1, column=2, padx=5, pady=5, sticky="nsew")
# 创建文本控件和滚动条
text = tk.Text(scroll_frame, height=8, width=20)
text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar = ttk.Scrollbar(scroll_frame, orient=tk.VERTICAL, command=text.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
text.configure(yscrollcommand=scrollbar.set)
# 添加示例文本
for i in range(20):
text.insert(tk.END, f"这是第 {i+1} 行文本\n")
# 行2:树形视图
tree_frame = ttk.LabelFrame(tab1, text="树形视图", padding=10)
tree_frame.grid(row=2, column=0, padx=5, pady=5, sticky="nsew", columnspan=3)
# 创建树形视图
columns = ("姓名", "年龄", "部门", "职位")
tree = ttk.Treeview(tree_frame, columns=columns, show="headings", height=6)
for col in columns:
tree.heading(col, text=col)
tree.column(col, width=100)
# 添加示例数据
sample_data = [
("张三", 28, "技术部", "工程师"),
("李四", 32, "市场部", "经理"),
("王五", 25, "人事部", "专员"),
("赵六", 35, "财务部", "主管"),
("钱七", 29, "技术部", "架构师")
]
for item in sample_data:
tree.insert("", tk.END, values=item)
# 添加滚动条
tree_scroll = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL, command=tree.yview)
tree.configure(yscrollcommand=tree_scroll.set)
tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
tree_scroll.pack(side=tk.RIGHT, fill=tk.Y)
def create_theme_config_tab(self):
"""创建主题配置标签页"""
tab2 = ttk.Frame(self.notebook)
self.notebook.add(tab2, text="主题配置")
# 使用网格布局
tab2.columnconfigure(0, weight=1)
tab2.columnconfigure(1, weight=1)
# 左侧:主题列表
list_frame = ttk.LabelFrame(tab2, text="可用主题", padding=10)
list_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
# 主题列表框
self.theme_listbox = tk.Listbox(list_frame, height=10)
self.theme_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 填充主题列表
for theme_name in self.theme_manager.get_theme_names():
theme_info = self.theme_manager.get_theme_info(theme_name)
display_name = f"{theme_name} - {theme_info['name']}"
self.theme_listbox.insert(tk.END, display_name)
# 右侧:主题详情
detail_frame = ttk.LabelFrame(tab2, text="主题详情", padding=10)
detail_frame.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
# 主题详情文本
self.theme_detail_text = tk.Text(detail_frame, height=10, width=30)
self.theme_detail_text.pack(fill=tk.BOTH, expand=True)
# 绑定选择事件
self.theme_listbox.bind("<<ListboxSelect>>", self.on_theme_select)
# 按钮区域
button_frame = ttk.Frame(tab2)
button_frame.grid(row=1, column=0, columnspan=2, pady=10, sticky="ew")
ttk.Button(
button_frame,
text="应用选中主题",
command=self.apply_selected_theme
).pack(side=tk.LEFT, padx=5)
ttk.Button(
button_frame,
text="导出当前主题",
command=self.export_theme
).pack(side=tk.LEFT, padx=5)
ttk.Button(
button_frame,
text="导入新主题",
command=self.import_theme
).pack(side=tk.LEFT, padx=5)
def create_preview_tab(self):
"""创建样式预览标签页"""
tab3 = ttk.Frame(self.notebook)
self.notebook.add(tab3, text="样式预览")
# 创建颜色预览
self.create_color_preview(tab3)
def create_color_preview(self, parent):
"""创建颜色预览"""
# 获取当前主题的颜色
current_theme = self.theme_manager.get_theme_info(self.theme_var.get())
if not current_theme:
return
colors = current_theme["colors"]
# 创建颜色网格
color_frame = ttk.LabelFrame(parent, text="颜色方案", padding=10)
color_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 使用Canvas绘制颜色预览
canvas = tk.Canvas(color_frame, bg=colors["background"], highlightthickness=0)
canvas.pack(fill=tk.BOTH, expand=True)
# 绘制颜色块
color_items = list(colors.items())
columns = 4
rows = (len(color_items) + columns - 1) // columns
canvas_width = 800
canvas_height = 400
block_width = canvas_width // columns
block_height = canvas_height // rows
for i, (name, color) in enumerate(color_items):
row = i // columns
col = i % columns
x1 = col * block_width
y1 = row * block_height
x2 = x1 + block_width
y2 = y1 + block_height
# 绘制颜色块
canvas.create_rectangle(x1, y1, x2, y2, fill=color, outline=colors["border"])
# 添加文本
text_x = x1 + block_width // 2
text_y = y1 + block_height // 2
# 根据颜色亮度决定文本颜色
text_color = self._get_contrast_color(color)
canvas.create_text(
text_x, text_y - 10,
text=name,
fill=text_color,
font=("Arial", 10)
)
canvas.create_text(
text_x, text_y + 10,
text=color,
fill=text_color,
font=("Arial", 8)
)
def _get_contrast_color(self, hex_color: str) -> str:
"""根据背景色获取对比色(黑白)"""
# 简化实现:在实际应用中应该计算颜色亮度
if hex_color.startswith("#"):
hex_color = hex_color[1:]
if len(hex_color) == 6:
r = int(hex_color[0:2], 16)
g = int(hex_color[2:4], 16)
b = int(hex_color[4:6], 16)
# 计算相对亮度
luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
return "#000000" if luminance > 0.5 else "#ffffff"
return "#000000"
def create_statusbar(self, parent):
"""创建状态栏"""
self.statusbar = ttk.Frame(parent, relief=tk.SUNKEN, borderwidth=1)
self.statusbar.grid(row=2, column=0, sticky="ew", pady=(10, 0))
# 左侧状态信息
self.status_label = ttk.Label(
self.statusbar,
text="就绪 | 当前主题: 浅色主题"
)
self.status_label.pack(side=tk.LEFT, padx=10, pady=2)
# 右侧系统信息
ttk.Label(
self.statusbar,
text="Tkinter主题系统演示 v1.0"
).pack(side=tk.RIGHT, padx=10, pady=2)
def on_theme_change(self, event=None):
"""主题变更事件"""
theme_name = self.theme_var.get()
self.apply_theme(theme_name)
def apply_theme(self, theme_name: str):
"""应用主题"""
success = self.theme_manager.apply_theme(self.root, theme_name)
if success:
# 更新状态栏
theme_info = self.theme_manager.get_theme_info(theme_name)
self.status_label.config(text=f"就绪 | 当前主题: {theme_info['name']}")
# 刷新界面
self.refresh_ui()
def apply_selected_theme(self):
"""应用选中的主题"""
selection = self.theme_listbox.curselection()
if selection:
index = selection[0]
theme_names = self.theme_manager.get_theme_names()
if index < len(theme_names):
theme_name = theme_names[index]
self.theme_var.set(theme_name)
self.apply_theme(theme_name)
def on_theme_select(self, event):
"""主题选择事件"""
selection = self.theme_listbox.curselection()
if selection:
index = selection[0]
theme_names = self.theme_manager.get_theme_names()
if index < len(theme_names):
theme_name = theme_names[index]
theme_info = self.theme_manager.get_theme_info(theme_name)
# 清空文本
self.theme_detail_text.delete(1.0, tk.END)
# 显示主题详情
if theme_info:
detail_text = f"主题名称: {theme_info['name']}\n"
detail_text += f"主题类型: {theme_info['type']}\n\n"
detail_text += "颜色配置:\n"
for key, value in theme_info['colors'].items():
detail_text += f" {key}: {value}\n"
self.theme_detail_text.insert(1.0, detail_text)
def export_theme(self):
"""导出主题"""
# 实现导出逻辑
print("导出主题功能")
def import_theme(self):
"""导入主题"""
# 实现导入逻辑
print("导入主题功能")
def refresh_ui(self):
"""刷新界面"""
# 重新创建预览标签页
for i in range(self.notebook.index("end")):
if self.notebook.tab(i, "text") == "样式预览":
# 删除并重新创建预览标签页
self.notebook.forget(i)
self.create_preview_tab()
break
def show_about(self):
"""显示关于对话框"""
about_dialog = tk.Toplevel(self.root)
about_dialog.title("关于")
about_dialog.geometry("300x200")
about_dialog.transient(self.root)
about_dialog.grab_set()
# 居中显示
about_dialog.update_idletasks()
width = about_dialog.winfo_width()
height = about_dialog.winfo_height()
parent_x = self.root.winfo_rootx()
parent_y = self.root.winfo_rooty()
parent_width = self.root.winfo_width()
parent_height = self.root.winfo_height()
x = parent_x + (parent_width - width) // 2
y = parent_y + (parent_height - height) // 2
about_dialog.geometry(f"{width}x{height}+{x}+{y}")
# 关于内容
ttk.Label(
about_dialog,
text="现代化主题系统演示",
font=("Arial", 14, "bold")
).pack(pady=20)
ttk.Label(
about_dialog,
text="基于Tkinter/ttk的现代化主题系统\n\n"
"演示了完整的主题管理功能,\n"
"包括主题切换、样式配置和实时预览。"
).pack(pady=10)
ttk.Button(
about_dialog,
text="关闭",
command=about_dialog.destroy
).pack(pady=20)
def on_closing(self):
"""窗口关闭事件"""
if tk.messagebox.askokcancel("退出", "确定要退出程序吗?"):
self.root.destroy()
def run(self):
"""运行应用程序"""
self.root.mainloop()
if __name__ == "__main__":
app = ThemeDemoApplication()
app.run()
# 由于篇幅限制,我们暂时到这里
# 后续内容将包括:第三方的ttk主题库对比、实战Demo工程等
# 完整内容将在后续部分继续提供
3.2 第三方ttk主题库深度对比
3.2.1 ttkbootstrap:Bootstrap风格的现代化主题
核心特点:
-
完全仿照Bootstrap 5设计,提供熟悉的视觉语言
-
支持完整的亮色/暗色主题切换
-
丰富的预定义样式类(primary, success, danger, warning, info等)
-
内置Tableview、Dialog、Toast等高级组件
-
活跃的社区和良好的文档支持
安装与使用:
bash
pip install ttkbootstrap
示例代码:创建Bootstrap风格的界面
python
"""
ttkbootstrap完整示例
展示Bootstrap风格在Tkinter中的应用
"""
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from ttkbootstrap.tableview import Tableview
from ttkbootstrap.dialogs import Messagebox, Querybox
from ttkbootstrap.toast import ToastNotification
from ttkbootstrap.style import Style
import json
from datetime import datetime
import random
class BootstrapDemoApp:
"""Bootstrap风格演示应用"""
def __init__(self):
# 创建应用,选择主题
self.root = ttk.Window(
title="ttkbootstrap演示应用",
themename="superhero", # 暗色主题
size=(1200, 800),
resizable=(True, True)
)
# 设置窗口图标
self.root.iconbitmap(default="")
# 初始化数据
self.setup_data()
# 创建界面
self.create_ui()
# 绑定事件
self.bind_events()
# 启动定时器
self.start_timers()
def setup_data(self):
"""设置示例数据"""
self.users = [
{"id": 1, "name": "张三", "email": "zhangsan@example.com", "role": "管理员", "status": "活跃"},
{"id": 2, "name": "李四", "email": "lisi@example.com", "role": "用户", "status": "活跃"},
{"id": 3, "name": "王五", "email": "wangwu@example.com", "role": "用户", "status": "禁用"},
{"id": 4, "name": "赵六", "email": "zhaoliu@example.com", "role": "编辑", "status": "活跃"},
{"id": 5, "name": "钱七", "email": "qianqi@example.com", "role": "用户", "status": "活跃"}
]
self.stats = {
"users": 1234,
"orders": 567,
"revenue": 89123.45,
"growth": 12.5
}
def create_ui(self):
"""创建用户界面"""
# 创建笔记本控件
self.notebook = ttk.Notebook(self.root, bootstyle=PRIMARY)
self.notebook.pack(fill=BOTH, expand=True, padx=10, pady=10)
# 创建各个标签页
self.create_dashboard_tab()
self.create_users_tab()
self.create_settings_tab()
self.create_components_tab()
def create_dashboard_tab(self):
"""创建仪表盘标签页"""
tab = ttk.Frame(self.notebook)
self.notebook.add(tab, text="仪表盘")
# 1. 顶部工具栏
toolbar = ttk.Frame(tab)
toolbar.pack(fill=X, padx=10, pady=(10, 0))
# 左侧:刷新按钮
ttk.Button(
toolbar,
text="刷新数据",
bootstyle=SUCCESS,
command=self.refresh_data
).pack(side=LEFT, padx=(0, 10))
# 右侧:日期选择
date_frame = ttk.Frame(toolbar)
date_frame.pack(side=RIGHT)
ttk.Label(date_frame, text="日期范围:").pack(side=LEFT, padx=(0, 5))
self.date_start = ttk.DateEntry(date_frame, bootstyle=PRIMARY)
self.date_start.pack(side=LEFT, padx=2)
ttk.Label(date_frame, text="至").pack(side=LEFT, padx=5)
self.date_end = ttk.DateEntry(date_frame, bootstyle=PRIMARY)
self.date_end.pack(side=LEFT, padx=2)
# 2. 统计卡片
self.create_stat_cards(tab)
# 3. 图表区域
self.create_charts_area(tab)
# 4. 最近活动
self.create_recent_activity(tab)
def create_stat_cards(self, parent):
"""创建统计卡片"""
cards_frame = ttk.Frame(parent)
cards_frame.pack(fill=X, padx=10, pady=10)
# 定义卡片数据
card_data = [
{
"title": "总用户数",
"value": f"{self.stats['users']:,}",
"icon": "👥",
"trend": "+12%",
"trend_up": True,
"style": PRIMARY
},
{
"title": "订单数量",
"value": f"{self.stats['orders']:,}",
"icon": "📦",
"trend": "+8%",
"trend_up": True,
"style": INFO
},
{
"title": "总收入",
"value": f"¥{self.stats['revenue']:,.2f}",
"icon": "💰",
"trend": "+15%",
"trend_up": True,
"style": SUCCESS
},
{
"title": "增长率",
"value": f"{self.stats['growth']}%",
"icon": "📈",
"trend": "+2%",
"trend_up": True,
"style": WARNING
}
]
for i, card in enumerate(card_data):
# 创建卡片框架
card_frame = ttk.Frame(cards_frame, bootstyle=card["style"])
card_frame.grid(row=0, column=i, padx=5, sticky=NSEW)
cards_frame.columnconfigure(i, weight=1)
# 设置内边距
inner_frame = ttk.Frame(card_frame, padding=15)
inner_frame.pack(fill=BOTH, expand=True)
# 图标
ttk.Label(
inner_frame,
text=card["icon"],
font=("Segoe UI", 24),
bootstyle=INVERSE
).pack(anchor=NW)
# 数值
ttk.Label(
inner_frame,
text=card["value"],
font=("Segoe UI", 24, "bold"),
bootstyle=INVERSE
).pack(pady=(10, 0))
# 标题
ttk.Label(
inner_frame,
text=card["title"],
font=("Segoe UI", 10),
bootstyle=INVERSE
).pack()
# 趋势
trend_frame = ttk.Frame(inner_frame)
trend_frame.pack(fill=X, pady=(10, 0))
trend_icon = "↗️" if card["trend_up"] else "↘️"
trend_color = SUCCESS if card["trend_up"] else DANGER
ttk.Label(
trend_frame,
text=trend_icon,
font=("Segoe UI", 10),
bootstyle=INVERSE
).pack(side=LEFT)
ttk.Label(
trend_frame,
text=card["trend"],
font=("Segoe UI", 9),
bootstyle=f"{trend_color}-inverse"
).pack(side=LEFT, padx=(5, 0))
def create_charts_area(self, parent):
"""创建图表区域"""
# 创建两列布局
chart_frame = ttk.Frame(parent)
chart_frame.pack(fill=BOTH, expand=True, padx=10, pady=(0, 10))
# 左侧:折线图
left_chart = ttk.Labelframe(
chart_frame,
text="用户增长趋势",
bootstyle=INFO,
padding=10
)
left_chart.grid(row=0, column=0, sticky=NSEW, padx=(0, 5))
# 创建Canvas绘制图表
self.line_chart_canvas = ttk.Canvas(
left_chart,
height=200,
highlightthickness=0
)
self.line_chart_canvas.pack(fill=BOTH, expand=True)
# 右侧:饼图
right_chart = ttk.Labelframe(
chart_frame,
text="用户分布",
bootstyle=INFO,
padding=10
)
right_chart.grid(row=0, column=1, sticky=NSEW, padx=(5, 0))
self.pie_chart_canvas = ttk.Canvas(
right_chart,
height=200,
highlightthickness=0
)
self.pie_chart_canvas.pack(fill=BOTH, expand=True)
# 配置网格权重
chart_frame.columnconfigure(0, weight=1)
chart_frame.columnconfigure(1, weight=1)
chart_frame.rowconfigure(0, weight=1)
# 绘制图表
self.draw_line_chart()
self.draw_pie_chart()
def draw_line_chart(self):
"""绘制折线图"""
canvas = self.line_chart_canvas
canvas.delete("all")
width = canvas.winfo_width() if canvas.winfo_width() > 1 else 400
height = canvas.winfo_height() if canvas.winfo_height() > 1 else 200
if width <= 1 or height <= 1:
canvas.after(100, self.draw_line_chart)
return
# 边距
margin = 40
chart_width = width - 2 * margin
chart_height = height - 2 * margin
# 绘制坐标轴
canvas.create_line(
margin, height - margin,
width - margin, height - margin,
width=2, fill="#6c757d" # 灰色
) # X轴
canvas.create_line(
margin, margin,
margin, height - margin,
width=2, fill="#6c757d"
) # Y轴
# 生成示例数据
months = ["1月", "2月", "3月", "4月", "5月", "6月"]
data = [random.randint(80, 150) for _ in range(6)]
# 绘制折线
x_step = chart_width / (len(months) - 1)
for i in range(len(data) - 1):
x1 = margin + i * x_step
y1 = height - margin - (data[i] / 200) * chart_height
x2 = margin + (i + 1) * x_step
y2 = height - margin - (data[i+1] / 200) * chart_height
canvas.create_line(
x1, y1, x2, y2,
fill="#0dcaf0", # Bootstrap info颜色
width=3
)
# 绘制点
canvas.create_oval(
x1-4, y1-4, x1+4, y1+4,
fill="#ffffff", outline="#0dcaf0", width=2
)
# 绘制最后一个点
if data:
x_last = margin + (len(data) - 1) * x_step
y_last = height - margin - (data[-1] / 200) * chart_height
canvas.create_oval(
x_last-4, y_last-4, x_last+4, y_last+4,
fill="#ffffff", outline="#0dcaf0", width=2
)
# 添加X轴标签
for i, month in enumerate(months):
x = margin + i * x_step
canvas.create_text(
x, height - margin + 15,
text=month,
font=("Segoe UI", 8),
fill="#adb5bd"
)
# 添加Y轴标签
for i in range(0, 201, 50):
y = height - margin - (i / 200) * chart_height
canvas.create_text(
margin - 10, y,
text=str(i),
font=("Segoe UI", 8),
fill="#adb5bd",
anchor=E
)
def draw_pie_chart(self):
"""绘制饼图"""
canvas = self.pie_chart_canvas
canvas.delete("all")
width = canvas.winfo_width() if canvas.winfo_width() > 1 else 400
height = canvas.winfo_height() if canvas.winfo_height() > 1 else 200
if width <= 1 or height <= 1:
canvas.after(100, self.draw_pie_chart)
return
# 饼图数据
categories = ["活跃用户", "新用户", "沉默用户", "流失用户"]
data = [45, 25, 20, 10] # 百分比
# Bootstrap颜色
colors = ["#0d6efd", "#198754", "#ffc107", "#dc3545"]
# 中心点
center_x = width // 2
center_y = height // 2
radius = min(center_x, center_y) - 20
# 绘制饼图
start_angle = 0
for i, (category, value) in enumerate(zip(categories, data)):
# 计算扇形的结束角度
end_angle = start_angle + (value * 3.6) # 百分比转角度
# 绘制扇形
canvas.create_arc(
center_x - radius, center_y - radius,
center_x + radius, center_y + radius,
start=start_angle,
extent=value * 3.6,
fill=colors[i],
outline="white",
width=2
)
# 计算标签位置
mid_angle = start_angle + (value * 1.8)
rad = mid_angle * 3.14159 / 180
label_x = center_x + (radius + 20) * 0.7 * 3.14159 * 0.5
label_y = center_y + (radius + 20) * 0.7 * 3.14159 * 0.5
# 绘制标签
canvas.create_text(
label_x, label_y,
text=f"{category}: {value}%",
font=("Segoe UI", 9),
fill=colors[i],
anchor=W
)
start_angle = end_angle
# 添加标题
canvas.create_text(
center_x, center_y,
text="用户分布",
font=("Segoe UI", 10, "bold"),
fill="#6c757d"
)
def create_recent_activity(self, parent):
"""创建最近活动列表"""
activity_frame = ttk.Labelframe(
parent,
text="最近活动",
bootstyle=INFO,
padding=10
)
activity_frame.pack(fill=BOTH, expand=True, padx=10, pady=(0, 10))
# 创建Tableview
coldata = [
{"text": "时间", "stretch": False},
{"text": "用户", "stretch": False},
{"text": "操作", "stretch": True},
{"text": "状态", "stretch": False}
]
rowdata = [
("10:30", "张三", "登录系统", "成功"),
("10:25", "李四", "修改个人资料", "成功"),
("10:20", "王五", "删除文件", "失败"),
("10:15", "赵六", "创建订单", "成功"),
("10:10", "钱七", "导出数据", "成功"),
("10:05", "孙八", "系统备份", "进行中"),
("10:00", "周九", "发送通知", "成功")
]
self.activity_table = Tableview(
activity_frame,
coldata=coldata,
rowdata=rowdata,
paginated=False,
searchable=False,
bootstyle=PRIMARY,
stripecolor=("", ""), # 禁用条纹
height=8
)
self.activity_table.pack(fill=BOTH, expand=True)
def create_users_tab(self):
"""创建用户管理标签页"""
tab = ttk.Frame(self.notebook)
self.notebook.add(tab, text="用户管理")
# 工具栏
toolbar = ttk.Frame(tab, padding=10)
toolbar.pack(fill=X)
# 左侧按钮组
left_buttons = ttk.Frame(toolbar)
left_buttons.pack(side=LEFT)
ttk.Button(
left_buttons,
text="添加用户",
bootstyle=SUCCESS,
command=self.add_user
).pack(side=LEFT, padx=2)
ttk.Button(
left_buttons,
text="编辑用户",
bootstyle=WARNING,
command=self.edit_user
).pack(side=LEFT, padx=2)
ttk.Button(
left_buttons,
text="删除用户",
bootstyle=DANGER,
command=self.delete_user
).pack(side=LEFT, padx=2)
ttk.Button(
left_buttons,
text="刷新",
bootstyle=INFO,
command=self.refresh_users
).pack(side=LEFT, padx=2)
# 右侧搜索框
search_frame = ttk.Frame(toolbar)
search_frame.pack(side=RIGHT)
ttk.Label(search_frame, text="搜索:").pack(side=LEFT, padx=(0, 5))
self.search_var = ttk.StringVar()
search_entry = ttk.Entry(
search_frame,
textvariable=self.search_var,
width=20
)
search_entry.pack(side=LEFT, padx=(0, 5))
ttk.Button(
search_frame,
text="搜索",
bootstyle=SECONDARY,
command=self.search_users
).pack(side=LEFT)
# 用户表格
self.create_users_table(tab)
def create_users_table(self, parent):
"""创建用户表格"""
table_frame = ttk.Frame(parent)
table_frame.pack(fill=BOTH, expand=True, padx=10, pady=(0, 10))
# 定义列
coldata = [
{"text": "ID", "stretch": False, "width": 50},
{"text": "姓名", "stretch": True},
{"text": "邮箱", "stretch": True},
{"text": "角色", "stretch": False, "width": 100},
{"text": "状态", "stretch": False, "width": 100}
]
# 转换数据
rowdata = []
for user in self.users:
status_style = SUCCESS if user["status"] == "活跃" else DANGER
rowdata.append((
user["id"],
user["name"],
user["email"],
user["role"],
(user["status"], status_style)
))
# 创建Tableview
self.users_table = Tableview(
table_frame,
coldata=coldata,
rowdata=rowdata,
paginated=True,
searchable=True,
bootstyle=PRIMARY,
stripecolor=("#f8f9fa", None),
autofit=True,
height=15
)
self.users_table.pack(fill=BOTH, expand=True)
def create_settings_tab(self):
"""创建设置标签页"""
tab = ttk.Frame(self.notebook)
self.notebook.add(tab, text="系统设置")
# 使用Notebook组织设置分类
settings_notebook = ttk.Notebook(tab, bootstyle=PRIMARY)
settings_notebook.pack(fill=BOTH, expand=True, padx=10, pady=10)
# 常规设置
general_tab = ttk.Frame(settings_notebook)
settings_notebook.add(general_tab, text="常规")
self.create_general_settings(general_tab)
# 外观设置
appearance_tab = ttk.Frame(settings_notebook)
settings_notebook.add(appearance_tab, text="外观")
self.create_appearance_settings(appearance_tab)
# 通知设置
notification_tab = ttk.Frame(settings_notebook)
settings_notebook.add(notification_tab, text="通知")
self.create_notification_settings(notification_tab)
# 按钮区域
button_frame = ttk.Frame(tab)
button_frame.pack(fill=X, padx=10, pady=(0, 10))
ttk.Button(
button_frame,
text="保存设置",
bootstyle=SUCCESS,
command=self.save_settings
).pack(side=RIGHT, padx=2)
ttk.Button(
button_frame,
text="恢复默认",
bootstyle=SECONDARY,
command=self.reset_settings
).pack(side=RIGHT, padx=2)
def create_general_settings(self, parent):
"""创建常规设置"""
# 滚动区域
canvas = ttk.Canvas(parent)
scrollbar = ttk.Scrollbar(parent, orient=VERTICAL, command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor=NW)
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side=LEFT, fill=BOTH, expand=True)
scrollbar.pack(side=RIGHT, fill=Y)
# 设置内容
content_frame = ttk.Frame(scrollable_frame, padding=20)
content_frame.pack(fill=BOTH, expand=True)
# 应用名称
ttk.Label(
content_frame,
text="应用名称:",
font=("Segoe UI", 10, "bold")
).grid(row=0, column=0, sticky=W, pady=(0, 5))
self.app_name_var = ttk.StringVar(value="企业管理后台")
ttk.Entry(
content_frame,
textvariable=self.app_name_var,
width=30
).grid(row=0, column=1, sticky=W, pady=(0, 5), padx=(10, 0))
# 时区设置
ttk.Label(
content_frame,
text="时区:",
font=("Segoe UI", 10, "bold")
).grid(row=1, column=0, sticky=W, pady=(0, 5))
self.timezone_var = ttk.StringVar(value="Asia/Shanghai")
timezone_combo = ttk.Combobox(
content_frame,
textvariable=self.timezone_var,
values=["Asia/Shanghai", "America/New_York", "Europe/London", "Asia/Tokyo"],
width=28
)
timezone_combo.grid(row=1, column=1, sticky=W, pady=(0, 5), padx=(10, 0))
# 自动保存
ttk.Label(
content_frame,
text="自动保存:",
font=("Segoe UI", 10, "bold")
).grid(row=2, column=0, sticky=W, pady=(0, 5))
self.auto_save_var = ttk.BooleanVar(value=True)
ttk.Checkbutton(
content_frame,
text="启用自动保存",
variable=self.auto_save_var,
bootstyle="round-toggle"
).grid(row=2, column=1, sticky=W, pady=(0, 5), padx=(10, 0))
# 保存间隔
ttk.Label(
content_frame,
text="保存间隔(分钟):",
font=("Segoe UI", 10, "bold")
).grid(row=3, column=0, sticky=W, pady=(0, 5))
self.save_interval_var = ttk.IntVar(value=5)
ttk.Scale(
content_frame,
from_=1,
to=60,
variable=self.save_interval_var,
bootstyle=PRIMARY
).grid(row=3, column=1, sticky=W, pady=(0, 5), padx=(10, 0))
# 配置网格
content_frame.columnconfigure(1, weight=1)
def create_appearance_settings(self, parent):
"""创建外观设置"""
# 主题选择
ttk.Label(
parent,
text="选择主题:",
font=("Segoe UI", 12, "bold")
).pack(anchor=W, padx=20, pady=(20, 10))
# 主题网格
theme_frame = ttk.Frame(parent)
theme_frame.pack(fill=X, padx=20, pady=(0, 20))
themes = [
("superhero", "深色主题", "#212529", "🌙"),
("flatly", "浅色主题", "#ffffff", "☀️"),
("cyborg", "深色科技", "#060606", "⚡"),
("journal", "简约白", "#ffffff", "📄"),
("darkly", "暗黑主题", "#222222", "🌌"),
("solar", "太阳主题", "#002b36", "🌅")
]
self.theme_var = ttk.StringVar(value="superhero")
for i, (theme_id, name, bg_color, icon) in enumerate(themes):
col = i % 3
row = i // 3
theme_card = ttk.Frame(theme_frame, bootstyle=SECONDARY)
theme_card.grid(row=row, column=col, padx=5, pady=5, sticky=NSEW)
# 设置网格权重
theme_frame.columnconfigure(col, weight=1)
if row == len(themes) // 3:
theme_frame.rowconfigure(row, weight=1)
# 单选按钮
radio = ttk.Radiobutton(
theme_card,
text=f"{icon} {name}",
variable=self.theme_var,
value=theme_id,
command=lambda t=theme_id: self.change_theme(t),
bootstyle="toolbutton"
)
radio.pack(fill=BOTH, expand=True, padx=10, pady=10)
# 预览颜色
preview = ttk.Frame(theme_card, height=5)
preview.pack(fill=X, padx=10, pady=(0, 10))
# 设置背景色
preview.configure(bootstyle=SECONDARY)
def create_notification_settings(self, parent):
"""创建通知设置"""
ttk.Label(
parent,
text="通知设置",
font=("Segoe UI", 12, "bold")
).pack(anchor=W, padx=20, pady=(20, 10))
# 启用通知
self.notify_enable_var = ttk.BooleanVar(value=True)
ttk.Checkbutton(
parent,
text="启用系统通知",
variable=self.notify_enable_var,
bootstyle="round-toggle"
).pack(anchor=W, padx=20, pady=(0, 10))
# 通知类型
notify_frame = ttk.Labelframe(
parent,
text="通知类型",
bootstyle=INFO,
padding=10
)
notify_frame.pack(fill=X, padx=20, pady=(0, 10))
self.notify_types = {
"系统消息": ttk.BooleanVar(value=True),
"用户活动": ttk.BooleanVar(value=True),
"错误报告": ttk.BooleanVar(value=True),
"安全警告": ttk.BooleanVar(value=True)
}
for i, (name, var) in enumerate(self.notify_types.items()):
ttk.Checkbutton(
notify_frame,
text=name,
variable=var
).grid(row=i//2, column=i%2, sticky=W, padx=5, pady=2)
# 通知声音
ttk.Label(
parent,
text="通知声音:",
font=("Segoe UI", 10, "bold")
).pack(anchor=W, padx=20, pady=(10, 5))
self.notify_sound_var = ttk.StringVar(value="default")
sounds = ["默认", "提示音", "无声音"]
for sound in sounds:
ttk.Radiobutton(
parent,
text=sound,
variable=self.notify_sound_var,
value=sound.lower()
).pack(anchor=W, padx=40, pady=2)
def create_components_tab(self):
"""创建组件演示标签页"""
tab = ttk.Frame(self.notebook)
self.notebook.add(tab, text="组件演示")
# 滚动区域
canvas = ttk.Canvas(tab)
scrollbar = ttk.Scrollbar(tab, orient=VERTICAL, command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor=NW)
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side=LEFT, fill=BOTH, expand=True, padx=10, pady=10)
scrollbar.pack(side=RIGHT, fill=Y)
# 组件演示
self.demo_components(scrollable_frame)
def demo_components(self, parent):
"""演示各种组件"""
# 按钮演示
self.demo_buttons(parent)
# 输入控件演示
self.demo_inputs(parent)
# 进度条和滑块
self.demo_progress(parent)
# 对话框演示
self.demo_dialogs(parent)
def demo_buttons(self, parent):
"""演示按钮"""
ttk.Label(
parent,
text="按钮样式",
font=("Segoe UI", 14, "bold")
).pack(anchor=W, padx=10, pady=(20, 10))
# 按钮网格
button_frame = ttk.Frame(parent)
button_frame.pack(fill=X, padx=10, pady=(0, 20))
buttons = [
("主要按钮", PRIMARY),
("次要按钮", SECONDARY),
("成功按钮", SUCCESS),
("危险按钮", DANGER),
("警告按钮", WARNING),
("信息按钮", INFO),
("浅色按钮", LIGHT),
("深色按钮", DARK)
]
for i, (text, style) in enumerate(buttons):
row = i // 4
col = i % 4
btn = ttk.Button(
button_frame,
text=text,
bootstyle=style,
command=lambda t=text: self.show_toast(f"点击了{t}")
)
btn.grid(row=row, column=col, padx=5, pady=5, sticky=W)
button_frame.columnconfigure(col, weight=1)
def demo_inputs(self, parent):
"""演示输入控件"""
ttk.Label(
parent,
text="输入控件",
font=("Segoe UI", 14, "bold")
).pack(anchor=W, padx=10, pady=(0, 10))
input_frame = ttk.Frame(parent)
input_frame.pack(fill=X, padx=10, pady=(0, 20))
# 文本框
ttk.Label(input_frame, text="文本框:").grid(row=0, column=0, sticky=W, pady=5)
ttk.Entry(input_frame, width=30).grid(row=0, column=1, sticky=W, padx=(10, 0), pady=5)
# 密码框
ttk.Label(input_frame, text="密码框:").grid(row=1, column=0, sticky=W, pady=5)
ttk.Entry(input_frame, show="*", width=30).grid(row=1, column=1, sticky=W, padx=(10, 0), pady=5)
# 组合框
ttk.Label(input_frame, text="组合框:").grid(row=2, column=0, sticky=W, pady=5)
ttk.Combobox(
input_frame,
values=["选项1", "选项2", "选项3"],
width=28
).grid(row=2, column=1, sticky=W, padx=(10, 0), pady=5)
# 日期选择
ttk.Label(input_frame, text="日期选择:").grid(row=3, column=0, sticky=W, pady=5)
ttk.DateEntry(input_frame, width=28).grid(row=3, column=1, sticky=W, padx=(10, 0), pady=5)
# 复选框
ttk.Label(input_frame, text="复选框:").grid(row=4, column=0, sticky=W, pady=5)
check_frame = ttk.Frame(input_frame)
check_frame.grid(row=4, column=1, sticky=W, padx=(10, 0), pady=5)
ttk.Checkbutton(check_frame, text="选项1").pack(side=LEFT, padx=(0, 10))
ttk.Checkbutton(check_frame, text="选项2").pack(side=LEFT)
# 单选按钮
ttk.Label(input_frame, text="单选按钮:").grid(row=5, column=0, sticky=W, pady=5)
radio_frame = ttk.Frame(input_frame)
radio_frame.grid(row=5, column=1, sticky=W, padx=(10, 0), pady=5)
radio_var = ttk.StringVar(value="option1")
ttk.Radiobutton(radio_frame, text="选项1", variable=radio_var, value="option1").pack(side=LEFT, padx=(0, 10))
ttk.Radiobutton(radio_frame, text="选项2", variable=radio_var, value="option2").pack(side=LEFT)
input_frame.columnconfigure(1, weight=1)
def demo_progress(self, parent):
"""演示进度条和滑块"""
ttk.Label(
parent,
text="进度条和滑块",
font=("Segoe UI", 14, "bold")
).pack(anchor=W, padx=10, pady=(0, 10))
progress_frame = ttk.Frame(parent)
progress_frame.pack(fill=X, padx=10, pady=(0, 20))
# 进度条
ttk.Label(progress_frame, text="进度条:").grid(row=0, column=0, sticky=W, pady=5)
self.progress_var = ttk.IntVar(value=50)
ttk.Progressbar(
progress_frame,
variable=self.progress_var,
bootstyle=SUCCESS,
length=200
).grid(row=0, column=1, sticky=W, padx=(10, 0), pady=5)
# 滑块
ttk.Label(progress_frame, text="滑块:").grid(row=1, column=0, sticky=W, pady=5)
ttk.Scale(
progress_frame,
from_=0,
to=100,
variable=self.progress_var,
bootstyle=PRIMARY,
length=200
).grid(row=1, column=1, sticky=W, padx=(10, 0), pady=5)
# 当前值
ttk.Label(
progress_frame,
textvariable=self.progress_var
).grid(row=1, column=2, sticky=W, padx=(10, 0), pady=5)
progress_frame.columnconfigure(1, weight=1)
def demo_dialogs(self, parent):
"""演示对话框"""
ttk.Label(
parent,
text="对话框",
font=("Segoe UI", 14, "bold")
).pack(anchor=W, padx=10, pady=(0, 10))
dialog_frame = ttk.Frame(parent)
dialog_frame.pack(fill=X, padx=10, pady=(0, 20))
# 消息对话框
ttk.Button(
dialog_frame,
text="信息对话框",
bootstyle=INFO,
command=lambda: Messagebox.show_info("提示", "这是一个信息对话框")
).pack(side=LEFT, padx=5, pady=5)
# 警告对话框
ttk.Button(
dialog_frame,
text="警告对话框",
bootstyle=WARNING,
command=lambda: Messagebox.show_warning("警告", "这是一个警告对话框")
).pack(side=LEFT, padx=5, pady=5)
# 错误对话框
ttk.Button(
dialog_frame,
text="错误对话框",
bootstyle=DANGER,
command=lambda: Messagebox.show_error("错误", "这是一个错误对话框")
).pack(side=LEFT, padx=5, pady=5)
# 确认对话框
ttk.Button(
dialog_frame,
text="确认对话框",
bootstyle=PRIMARY,
command=lambda: Messagebox.show_question("确认", "这是一个确认对话框")
).pack(side=LEFT, padx=5, pady=5)
# 输入对话框
ttk.Button(
dialog_frame,
text="输入对话框",
bootstyle=SUCCESS,
command=lambda: Querybox.get_string("输入", "请输入内容")
).pack(side=LEFT, padx=5, pady=5)
# Toast通知
ttk.Button(
dialog_frame,
text="Toast通知",
bootstyle=SECONDARY,
command=self.show_toast
).pack(side=LEFT, padx=5, pady=5)
def bind_events(self):
"""绑定事件"""
# 定时刷新图表
self.root.bind("<Control-r>", lambda e: self.refresh_data())
# 主题切换快捷键
self.root.bind("<Control-t>", lambda e: self.toggle_theme())
def start_timers(self):
"""启动定时器"""
# 每5秒更新进度条
self.update_progress()
# 每10秒更新图表
self.root.after(10000, self.update_charts)
def update_progress(self):
"""更新进度条"""
current = self.progress_var.get()
if current >= 100:
self.progress_var.set(0)
else:
self.progress_var.set(current + 5)
self.root.after(500, self.update_progress)
def update_charts(self):
"""更新图表"""
self.draw_line_chart()
self.draw_pie_chart()
self.root.after(10000, self.update_charts)
def refresh_data(self):
"""刷新数据"""
# 更新统计数据
self.stats = {
"users": random.randint(1000, 2000),
"orders": random.randint(500, 1000),
"revenue": random.uniform(50000, 150000),
"growth": random.uniform(5, 20)
}
# 显示Toast通知
self.show_toast("数据已刷新")
# 重新绘制图表
self.draw_line_chart()
self.draw_pie_chart()
def add_user(self):
"""添加用户"""
result = Querybox.get_string("添加用户", "请输入用户名:")
if result:
new_id = max(user["id"] for user in self.users) + 1
self.users.append({
"id": new_id,
"name": result,
"email": f"{result.lower()}@example.com",
"role": "用户",
"status": "活跃"
})
self.refresh_users()
self.show_toast(f"用户 {result} 已添加")
def edit_user(self):
"""编辑用户"""
selected = self.users_table.get_rows(selected=True)
if not selected:
Messagebox.show_warning("提示", "请先选择要编辑的用户")
return
user_id = selected[0].values[0]
Messagebox.show_info("编辑用户", f"编辑用户ID: {user_id}")
def delete_user(self):
"""删除用户"""
selected = self.users_table.get_rows(selected=True)
if not selected:
Messagebox.show_warning("提示", "请先选择要删除的用户")
return
user_id = selected[0].values[0]
if Messagebox.show_question("确认删除", f"确定要删除用户ID: {user_id}吗?"):
self.users = [user for user in self.users if user["id"] != user_id]
self.refresh_users()
self.show_toast(f"用户ID: {user_id} 已删除")
def refresh_users(self):
"""刷新用户列表"""
# 重新创建表格
for widget in self.users_table.master.winfo_children():
widget.destroy()
self.create_users_table(self.users_table.master.master)
def search_users(self):
"""搜索用户"""
query = self.search_var.get().lower()
if not query:
self.refresh_users()
return
filtered_users = [
user for user in self.users
if query in user["name"].lower() or query in user["email"].lower()
]
# 显示搜索结果
if filtered_users:
self.show_toast(f"找到 {len(filtered_users)} 个用户")
else:
Messagebox.show_info("搜索结果", f"未找到包含 '{query}' 的用户")
def change_theme(self, theme_name):
"""切换主题"""
self.root.style.theme_use(theme_name)
self.show_toast(f"已切换至 {theme_name} 主题")
def toggle_theme(self):
"""切换主题(亮色/暗色)"""
current = self.root.style.theme.name
if "dark" in current or "superhero" in current or "cyborg" in current:
new_theme = "flatly"
else:
new_theme = "superhero"
self.change_theme(new_theme)
def save_settings(self):
"""保存设置"""
Messagebox.show_info("设置已保存", "所有设置已成功保存")
def reset_settings(self):
"""恢复默认设置"""
if Messagebox.show_question("确认", "确定要恢复默认设置吗?"):
self.show_toast("设置已恢复默认")
def show_toast(self, message="操作成功"):
"""显示Toast通知"""
ToastNotification(
title="系统提示",
message=message,
duration=3000,
alert=True,
bootstyle=SUCCESS
).show_toast()
def run(self):
"""运行应用"""
self.root.mainloop()
if __name__ == "__main__":
app = BootstrapDemoApp()
app.run()
3.2.2 customtkinter:高度可定制的现代控件
核心特点:
-
完全重新设计的控件,不依赖原生操作系统样式
-
支持亮色/暗色模式自动切换
-
高度可自定义的颜色、圆角、阴影等属性
-
内置现代控件如CTkSlider、CTkProgressBar、CTkSwitch等
-
提供Canvas绘制工具,支持自定义图形
安装与使用:
bash
pip install customtkinter
示例代码:创建现代化应用界面
python
"""
customtkinter完整示例
展示现代化UI控件的应用
"""
import customtkinter as ctk
from tkinter import messagebox
import json
import random
from datetime import datetime
import os
from typing import List, Dict, Any, Optional
class ModernApp(ctk.CTk):
"""现代化应用示例"""
def __init__(self):
super().__init__()
# 配置窗口
self.title("现代化应用示例")
self.geometry("1200x800")
# 设置外观模式
ctk.set_appearance_mode("dark") # 可选: "dark", "light", "system"
ctk.set_default_color_theme("blue") # 可选: "blue", "green", "dark-blue"
# 初始化数据
self.init_data()
# 创建界面
self.create_widgets()
# 启动定时器
self.after(100, self.update_ui)
# 设置窗口居中
self.center_window()
def init_data(self):
"""初始化数据"""
self.sample_data = {
"sales": [random.randint(1000, 5000) for _ in range(12)],
"users": [random.randint(100, 1000) for _ in range(12)],
"months": ["1月", "2月", "3月", "4月", "5月", "6月",
"7月", "8月", "9月", "10月", "11月", "12月"]
}
self.settings = {
"theme": "dark",
"notifications": True,
"auto_save": True,
"animation": True
}
# 用户数据
self.users = [
{"id": 1, "name": "张三", "email": "zhangsan@example.com", "role": "管理员", "status": "在线", "avatar": "👨💼"},
{"id": 2, "name": "李四", "email": "lisi@example.com", "role": "用户", "status": "在线", "avatar": "👩💻"},
{"id": 3, "name": "王五", "email": "wangwu@example.com", "role": "用户", "status": "离线", "avatar": "👨🔧"},
{"id": 4, "name": "赵六", "email": "zhaoliu@example.com", "role": "编辑", "status": "在线", "avatar": "👩🎨"},
{"id": 5, "name": "钱七", "email": "qianqi@example.com", "role": "用户", "status": "离开", "avatar": "👨🏫"}
]
def center_window(self):
"""窗口居中显示"""
self.update_idletasks()
width = self.winfo_width()
height = self.winfo_height()
screen_width = self.winfo_screenwidth()
screen_height = self.winfo_screenheight()
x = (screen_width - width) // 2
y = (screen_height - height) // 2
self.geometry(f"{width}x{height}+{x}+{y}")
def create_widgets(self):
"""创建控件"""
# 创建网格布局
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(1, weight=1)
# 1. 顶部工具栏
self.create_toolbar()
# 2. 左侧导航栏
self.create_sidebar()
# 3. 主内容区域
self.create_main_content()
# 4. 底部状态栏
self.create_statusbar()
def create_toolbar(self):
"""创建顶部工具栏"""
toolbar = ctk.CTkFrame(self, height=60, corner_radius=0)
toolbar.grid(row=0, column=0, columnspan=3, sticky="ew", padx=0, pady=0)
toolbar.grid_columnconfigure(1, weight=1)
# 左侧:应用标题
title_frame = ctk.CTkFrame(toolbar, fg_color="transparent")
title_frame.grid(row=0, column=0, padx=20, pady=10, sticky="w")
ctk.CTkLabel(
title_frame,
text="⚡",
font=ctk.CTkFont(size=24, weight="bold")
).pack(side="left", padx=(0, 10))
ctk.CTkLabel(
title_frame,
text="Modern Dashboard",
font=ctk.CTkFont(size=20, weight="bold")
).pack(side="left")
# 中间:搜索框
search_frame = ctk.CTkFrame(toolbar, fg_color="transparent")
search_frame.grid(row=0, column=1, padx=20, pady=10, sticky="ew")
search_frame.grid_columnconfigure(0, weight=1)
self.search_entry = ctk.CTkEntry(
search_frame,
placeholder_text="搜索...",
height=40
)
self.search_entry.grid(row=0, column=0, sticky="ew", padx=(0, 10))
search_btn = ctk.CTkButton(
search_frame,
text="搜索",
width=80,
height=40,
command=self.on_search
)
search_btn.grid(row=0, column=1)
# 右侧:用户信息和设置
user_frame = ctk.CTkFrame(toolbar, fg_color="transparent")
user_frame.grid(row=0, column=2, padx=20, pady=10, sticky="e")
# 通知按钮
notification_btn = ctk.CTkButton(
user_frame,
text="🔔",
width=40,
height=40,
fg_color="transparent",
hover_color=("gray70", "gray30"),
command=self.show_notifications
)
notification_btn.pack(side="left", padx=5)
# 用户头像
avatar_btn = ctk.CTkButton(
user_frame,
text="👤",
width=40,
height=40,
fg_color="transparent",
hover_color=("gray70", "gray30"),
command=self.show_user_menu
)
avatar_btn.pack(side="left", padx=5)
# 主题切换
self.theme_switch = ctk.CTkSwitch(
user_frame,
text="",
command=self.toggle_theme,
width=40
)
self.theme_switch.pack(side="left", padx=5)
# 根据当前主题设置开关状态
current_mode = ctk.get_appearance_mode()
if current_mode == "light":
self.theme_switch.select()
def create_sidebar(self):
"""创建左侧导航栏"""
sidebar = ctk.CTkFrame(self, width=200, corner_radius=0)
sidebar.grid(row=1, column=0, sticky="nsew", rowspan=2, padx=0, pady=0)
sidebar.grid_propagate(False)
sidebar.grid_columnconfigure(0, weight=1)
# Logo区域
logo_frame = ctk.CTkFrame(sidebar, fg_color="transparent")
logo_frame.grid(row=0, column=0, pady=(20, 10), padx=20, sticky="ew")
ctk.CTkLabel(
logo_frame,
text="⚡",
font=ctk.CTkFont(size=30, weight="bold")
).pack()
ctk.CTkLabel(
logo_frame,
text="ModernApp",
font=ctk.CTkFont(size=18, weight="bold")
).pack()
# 分隔线
ctk.CTkFrame(sidebar, height=2, fg_color=("gray70", "gray30")).grid(
row=1, column=0, sticky="ew", padx=20, pady=10
)
# 导航按钮
nav_items = [
("🏠", "仪表盘", self.show_dashboard),
("👥", "用户管理", self.show_users),
("📊", "数据分析", self.show_analytics),
("⚙️", "设置", self.show_settings),
("📁", "文件", self.show_files),
("🛠️", "工具", self.show_tools)
]
self.nav_buttons = []
for i, (icon, text, command) in enumerate(nav_items):
btn = ctk.CTkButton(
sidebar,
text=f" {icon} {text}",
font=ctk.CTkFont(size=14),
height=50,
anchor="w",
fg_color="transparent",
hover_color=("gray70", "gray30"),
command=command
)
btn.grid(row=i+2, column=0, sticky="ew", padx=10, pady=5)
self.nav_buttons.append(btn)
# 默认选中第一个按钮
self.select_nav_button(0)
# 底部区域
bottom_frame = ctk.CTkFrame(sidebar, fg_color="transparent")
bottom_frame.grid(row=len(nav_items)+3, column=0, sticky="sew", padx=20, pady=20)
bottom_frame.grid_columnconfigure(0, weight=1)
# 帮助按钮
help_btn = ctk.CTkButton(
bottom_frame,
text="❓ 帮助",
font=ctk.CTkFont(size=12),
height=40,
fg_color="transparent",
hover_color=("gray70", "gray30"),
command=self.show_help
)
help_btn.grid(row=0, column=0, sticky="ew", pady=(0, 10))
# 退出按钮
exit_btn = ctk.CTkButton(
bottom_frame,
text="🚪 退出",
font=ctk.CTkFont(size=12),
height=40,
fg_color="transparent",
hover_color=("gray70", "gray30"),
command=self.on_exit
)
exit_btn.grid(row=1, column=0, sticky="ew")
def create_main_content(self):
"""创建主内容区域"""
# 创建主内容框架
main_content = ctk.CTkFrame(self, fg_color="transparent")
main_content.grid(row=1, column=1, sticky="nsew", padx=20, pady=20)
main_content.grid_columnconfigure(0, weight=1)
main_content.grid_rowconfigure(1, weight=1)
# 内容标题
self.content_title = ctk.CTkLabel(
main_content,
text="仪表盘",
font=ctk.CTkFont(size=24, weight="bold")
)
self.content_title.grid(row=0, column=0, sticky="w", pady=(0, 20))
# 内容容器
self.content_container = ctk.CTkScrollableFrame(
main_content,
fg_color="transparent"
)
self.content_container.grid(row=1, column=0, sticky="nsew")
self.content_container.grid_columnconfigure(0, weight=1)
# 显示默认页面
self.show_dashboard()
def create_statusbar(self):
"""创建底部状态栏"""
statusbar = ctk.CTkFrame(self, height=40, corner_radius=0)
statusbar.grid(row=2, column=0, columnspan=2, sticky="ew", padx=0, pady=0)
# 左侧状态信息
self.status_label = ctk.CTkLabel(
statusbar,
text="就绪",
font=ctk.CTkFont(size=12)
)
self.status_label.pack(side="left", padx=20, pady=10)
# 右侧系统信息
self.time_label = ctk.CTkLabel(
statusbar,
text="",
font=ctk.CTkFont(size=12)
)
self.time_label.pack(side="right", padx=20, pady=10)
def show_dashboard(self):
"""显示仪表盘页面"""
self.clear_content()
self.content_title.configure(text="仪表盘")
self.select_nav_button(0)
# 创建统计卡片
self.create_stat_cards()
# 创建图表区域
self.create_charts_area()
# 创建最近活动
self.create_recent_activity()
def create_stat_cards(self):
"""创建统计卡片"""
# 卡片数据
card_data = [
{
"title": "总用户数",
"value": "1,234",
"change": "+12%",
"icon": "👥",
"color": "#4CC9F0"
},
{
"title": "总收入",
"value": "¥89,123",
"change": "+8%",
"icon": "💰",
"color": "#4361EE"
},
{
"title": "订单数",
"value": "567",
"change": "+15%",
"icon": "📦",
"color": "#3A0CA3"
},
{
"title": "增长率",
"value": "12.5%",
"change": "+2%",
"icon": "📈",
"color": "#7209B7"
}
]
# 创建卡片容器
cards_frame = ctk.CTkFrame(self.content_container, fg_color="transparent")
cards_frame.grid(row=0, column=0, sticky="ew", pady=(0, 20))
for i in range(4):
cards_frame.grid_columnconfigure(i, weight=1)
# 创建每个卡片
for i, card in enumerate(card_data):
card_frame = ctk.CTkFrame(
cards_frame,
width=200,
height=120,
corner_radius=15
)
card_frame.grid(row=0, column=i, padx=5, sticky="nsew")
# 卡片内容
inner_frame = ctk.CTkFrame(card_frame, fg_color="transparent")
inner_frame.pack(fill="both", expand=True, padx=20, pady=20)
# 图标和标题
icon_title_frame = ctk.CTkFrame(inner_frame, fg_color="transparent")
icon_title_frame.pack(fill="x", pady=(0, 10))
ctk.CTkLabel(
icon_title_frame,
text=card["icon"],
font=ctk.CTkFont(size=24)
).pack(side="left")
ctk.CTkLabel(
icon_title_frame,
text=card["title"],
font=ctk.CTkFont(size=12)
).pack(side="left", padx=(10, 0))
# 数值
ctk.CTkLabel(
inner_frame,
text=card["value"],
font=ctk.CTkFont(size=24, weight="bold")
).pack(anchor="w", pady=(0, 5))
# 变化
change_frame = ctk.CTkFrame(inner_frame, fg_color="transparent")
change_frame.pack(fill="x")
ctk.CTkLabel(
change_frame,
text=card["change"],
font=ctk.CTkFont(size=12),
text_color="#4CAF50" # 绿色表示增长
).pack(side="left")
ctk.CTkLabel(
change_frame,
text=" 本月",
font=ctk.CTkFont(size=10),
text_color="gray"
).pack(side="left")
def create_charts_area(self):
"""创建图表区域"""
# 图表容器
charts_frame = ctk.CTkFrame(self.content_container, fg_color="transparent")
charts_frame.grid(row=1, column=0, sticky="nsew", pady=(0, 20))
charts_frame.grid_columnconfigure(0, weight=1)
# 图表标题
ctk.CTkLabel(
charts_frame,
text="数据可视化",
font=ctk.CTkFont(size=18, weight="bold")
).grid(row=0, column=0, sticky="w", pady=(0, 20))
# 图表区域
chart_container = ctk.CTkFrame(charts_frame, height=300)
chart_container.grid(row=1, column=0, sticky="nsew", pady=(0, 20))
# 创建Canvas绘制图表
self.chart_canvas = ctk.CTkCanvas(
chart_container,
bg=ctk.ThemeManager.theme["CTkFrame"]["fg_color"][1],
highlightthickness=0
)
self.chart_canvas.pack(fill="both", expand=True, padx=20, pady=20)
# 绘制图表
self.draw_chart()
def draw_chart(self):
"""绘制图表"""
canvas = self.chart_canvas
canvas.delete("all")
width = canvas.winfo_width() if canvas.winfo_width() > 1 else 600
height = canvas.winfo_height() if canvas.winfo_height() > 1 else 300
if width <= 1 or height <= 1:
canvas.after(100, self.draw_chart)
return
# 边距
margin = 50
chart_width = width - 2 * margin
chart_height = height - 2 * margin
# 绘制坐标轴
canvas.create_line(
margin, height - margin,
width - margin, height - margin,
width=2, fill="#6c757d"
) # X轴
canvas.create_line(
margin, margin,
margin, height - margin,
width=2, fill="#6c757d"
) # Y轴
# 生成示例数据
months = self.sample_data["months"][:6]
sales = self.sample_data["sales"][:6]
# 绘制折线
if len(months) > 1:
x_step = chart_width / (len(months) - 1)
for i in range(len(sales) - 1):
x1 = margin + i * x_step
y1 = height - margin - (sales[i] / 5000) * chart_height
x2 = margin + (i + 1) * x_step
y2 = height - margin - (sales[i+1] / 5000) * chart_height
canvas.create_line(
x1, y1, x2, y2,
fill="#4CC9F0", # 蓝色
width=3
)
# 绘制点
canvas.create_oval(
x1-4, y1-4, x1+4, y1+4,
fill="#ffffff", outline="#4CC9F0", width=2
)
# 绘制最后一个点
if sales:
x_last = margin + (len(sales) - 1) * x_step
y_last = height - margin - (sales[-1] / 5000) * chart_height
canvas.create_oval(
x_last-4, y_last-4, x_last+4, y_last+4,
fill="#ffffff", outline="#4CC9F0", width=2
)
# 添加X轴标签
for i, month in enumerate(months):
x = margin + i * (chart_width / (len(months) - 1))
canvas.create_text(
x, height - margin + 15,
text=month,
font=("Arial", 10),
fill="#adb5bd"
)
# 添加Y轴标签
for i in range(0, 5001, 1000):
y = height - margin - (i / 5000) * chart_height
canvas.create_text(
margin - 10, y,
text=f"{i:,}",
font=("Arial", 10),
fill="#adb5bd",
anchor="e"
)
# 添加标题
canvas.create_text(
width // 2, 20,
text="销售额趋势 (1-6月)",
font=("Arial", 12, "bold"),
fill="#ffffff" if ctk.get_appearance_mode() == "dark" else "#000000"
)
def create_recent_activity(self):
"""创建最近活动"""
# 活动容器
activity_frame = ctk.CTkFrame(self.content_container, fg_color="transparent")
activity_frame.grid(row=2, column=0, sticky="nsew")
activity_frame.grid_columnconfigure(0, weight=1)
# 标题
title_frame = ctk.CTkFrame(activity_frame, fg_color="transparent")
title_frame.grid(row=0, column=0, sticky="ew", pady=(0, 20))
ctk.CTkLabel(
title_frame,
text="最近活动",
font=ctk.CTkFont(size=18, weight="bold")
).pack(side="left")
ctk.CTkButton(
title_frame,
text="查看全部",
width=80,
height=30,
font=ctk.CTkFont(size=12)
).pack(side="right")
# 活动列表
activities = [
{"user": "张三", "action": "登录系统", "time": "10分钟前", "icon": "🔐"},
{"user": "李四", "action": "创建了新项目", "time": "30分钟前", "icon": "🆕"},
{"user": "王五", "action": "更新了个人资料", "time": "1小时前", "icon": "👤"},
{"user": "赵六", "action": "提交了报告", "time": "2小时前", "icon": "📄"},
{"user": "钱七", "action": "添加了新用户", "time": "3小时前", "icon": "➕"}
]
for i, activity in enumerate(activities):
activity_item = ctk.CTkFrame(
activity_frame,
height=60,
corner_radius=10
)
activity_item.grid(row=i+1, column=0, sticky="ew", pady=(0, 10))
activity_item.grid_columnconfigure(1, weight=1)
# 图标
ctk.CTkLabel(
activity_item,
text=activity["icon"],
font=ctk.CTkFont(size=20)
).grid(row=0, column=0, padx=20, pady=20, sticky="w")
# 活动内容
content_frame = ctk.CTkFrame(activity_item, fg_color="transparent")
content_frame.grid(row=0, column=1, padx=(0, 20), pady=20, sticky="w")
ctk.CTkLabel(
content_frame,
text=f"{activity['user']} {activity['action']}",
font=ctk.CTkFont(size=14)
).pack(anchor="w")
ctk.CTkLabel(
content_frame,
text=activity["time"],
font=ctk.CTkFont(size=12),
text_color="gray"
).pack(anchor="w")
def show_users(self):
"""显示用户管理页面"""
self.clear_content()
self.content_title.configure(text="用户管理")
self.select_nav_button(1)
# 创建工具栏
self.create_users_toolbar()
# 创建用户表格
self.create_users_table()
def create_users_toolbar(self):
"""创建用户管理工具栏"""
toolbar = ctk.CTkFrame(self.content_container, fg_color="transparent")
toolbar.grid(row=0, column=0, sticky="ew", pady=(0, 20))
# 左侧按钮
left_frame = ctk.CTkFrame(toolbar, fg_color="transparent")
left_frame.pack(side="left")
buttons = [
("➕ 添加用户", self.add_user),
("✏️ 编辑", self.edit_user),
("🗑️ 删除", self.delete_user),
("🔄 刷新", self.refresh_users)
]
for text, command in buttons:
ctk.CTkButton(
left_frame,
text=text,
width=100,
height=40,
command=command
).pack(side="left", padx=5)
# 右侧搜索
right_frame = ctk.CTkFrame(toolbar, fg_color="transparent")
right_frame.pack(side="right")
self.user_search = ctk.CTkEntry(
right_frame,
placeholder_text="搜索用户...",
width=200,
height=40
)
self.user_search.pack(side="left", padx=(0, 10))
ctk.CTkButton(
right_frame,
text="搜索",
width=80,
height=40,
command=self.search_users
).pack(side="left")
def create_users_table(self):
"""创建用户表格"""
# 创建表格框架
table_frame = ctk.CTkFrame(self.content_container)
table_frame.grid(row=1, column=0, sticky="nsew", pady=(0, 20))
table_frame.grid_columnconfigure(0, weight=1)
table_frame.grid_rowconfigure(0, weight=1)
# 创建滚动区域
canvas = ctk.CTkCanvas(
table_frame,
bg=ctk.ThemeManager.theme["CTkFrame"]["fg_color"][1],
highlightthickness=0
)
canvas.grid(row=0, column=0, sticky="nsew")
# 创建内部框架
inner_frame = ctk.CTkFrame(canvas, fg_color="transparent")
canvas.create_window((0, 0), window=inner_frame, anchor="nw")
# 创建表头
headers = ["ID", "头像", "姓名", "邮箱", "角色", "状态", "操作"]
for i, header in enumerate(headers):
ctk.CTkLabel(
inner_frame,
text=header,
font=ctk.CTkFont(size=14, weight="bold"),
width=150 if i != 2 else 200,
height=50
).grid(row=0, column=i, padx=1, pady=1, sticky="nsew")
# 创建用户行
for row, user in enumerate(self.users, start=1):
# ID
ctk.CTkLabel(
inner_frame,
text=str(user["id"]),
width=150,
height=60
).grid(row=row, column=0, padx=1, pady=1, sticky="nsew")
# 头像
ctk.CTkLabel(
inner_frame,
text=user["avatar"],
width=150,
height=60,
font=ctk.CTkFont(size=20)
).grid(row=row, column=1, padx=1, pady=1, sticky="nsew")
# 姓名
ctk.CTkLabel(
inner_frame,
text=user["name"],
width=200,
height=60
).grid(row=row, column=2, padx=1, pady=1, sticky="nsew")
# 邮箱
ctk.CTkLabel(
inner_frame,
text=user["email"],
width=150,
height=60
).grid(row=row, column=3, padx=1, pady=1, sticky="nsew")
# 角色
role_label = ctk.CTkLabel(
inner_frame,
text=user["role"],
width=150,
height=60
)
# 设置角色颜色
if user["role"] == "管理员":
role_label.configure(fg_color="#4CAF50")
elif user["role"] == "编辑":
role_label.configure(fg_color="#2196F3")
else:
role_label.configure(fg_color="#FF9800")
role_label.grid(row=row, column=4, padx=1, pady=1, sticky="nsew")
# 状态
status_label = ctk.CTkLabel(
inner_frame,
text=user["status"],
width=150,
height=60
)
# 设置状态颜色
if user["status"] == "在线":
status_label.configure(fg_color="#4CAF50")
elif user["status"] == "离线":
status_label.configure(fg_color="#9E9E9E")
else:
status_label.configure(fg_color="#FF9800")
status_label.grid(row=row, column=5, padx=1, pady=1, sticky="nsew")
# 操作按钮
button_frame = ctk.CTkFrame(inner_frame, fg_color="transparent", width=150, height=60)
button_frame.grid(row=row, column=6, padx=1, pady=1, sticky="nsew")
button_frame.grid_propagate(False)
ctk.CTkButton(
button_frame,
text="编辑",
width=60,
height=30,
font=ctk.CTkFont(size=12),
command=lambda uid=user["id"]: self.edit_user_action(uid)
).pack(side="left", padx=2)
ctk.CTkButton(
button_frame,
text="删除",
width=60,
height=30,
font=ctk.CTkFont(size=12),
fg_color="#F44336",
hover_color="#D32F2F",
command=lambda uid=user["id"]: self.delete_user_action(uid)
).pack(side="left", padx=2)
# 更新滚动区域
inner_frame.update_idletasks()
canvas.configure(scrollregion=canvas.bbox("all"))
# 添加滚动条
scrollbar = ctk.CTkScrollbar(table_frame, orientation="vertical", command=canvas.yview)
scrollbar.grid(row=0, column=1, sticky="ns")
canvas.configure(yscrollcommand=scrollbar.set)
def show_analytics(self):
"""显示数据分析页面"""
self.clear_content()
self.content_title.configure(text="数据分析")
self.select_nav_button(2)
ctk.CTkLabel(
self.content_container,
text="数据分析页面正在开发中...",
font=ctk.CTkFont(size=20)
).pack(expand=True)
def show_settings(self):
"""显示设置页面"""
self.clear_content()
self.content_title.configure(text="设置")
self.select_nav_button(3)
# 创建设置表单
self.create_settings_form()
def create_settings_form(self):
"""创建设置表单"""
# 设置容器
settings_frame = ctk.CTkFrame(self.content_container, fg_color="transparent")
settings_frame.grid(row=0, column=0, sticky="nsew", pady=(0, 20))
settings_frame.grid_columnconfigure(1, weight=1)
# 主题设置
ctk.CTkLabel(
settings_frame,
text="外观设置",
font=ctk.CTkFont(size=16, weight="bold")
).grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 20))
# 主题选择
ctk.CTkLabel(
settings_frame,
text="主题模式:",
font=ctk.CTkFont(size=14)
).grid(row=1, column=0, sticky="w", pady=10)
theme_options = ["系统", "浅色", "深色"]
self.theme_var = ctk.StringVar(value=ctk.get_appearance_mode())
for i, option in enumerate(theme_options):
ctk.CTkRadioButton(
settings_frame,
text=option,
variable=self.theme_var,
value=option.lower(),
command=self.change_appearance_mode
).grid(row=1, column=i+1, sticky="w", padx=20, pady=10)
# 通知设置
ctk.CTkLabel(
settings_frame,
text="通知设置",
font=ctk.CTkFont(size=16, weight="bold")
).grid(row=2, column=0, columnspan=2, sticky="w", pady=(20, 10))
self.notify_var = ctk.BooleanVar(value=True)
ctk.CTkCheckBox(
settings_frame,
text="启用系统通知",
variable=self.notify_var,
font=ctk.CTkFont(size=14)
).grid(row=3, column=0, columnspan=2, sticky="w", pady=5)
# 自动保存
self.auto_save_var = ctk.BooleanVar(value=True)
ctk.CTkCheckBox(
settings_frame,
text="启用自动保存",
variable=self.auto_save_var,
font=ctk.CTkFont(size=14)
).grid(row=4, column=0, columnspan=2, sticky="w", pady=5)
# 动画效果
self.animation_var = ctk.BooleanVar(value=True)
ctk.CTkCheckBox(
settings_frame,
text="启用动画效果",
variable=self.animation_var,
font=ctk.CTkFont(size=14)
).grid(row=5, column=0, columnspan=2, sticky="w", pady=5)
# 按钮区域
button_frame = ctk.CTkFrame(settings_frame, fg_color="transparent")
button_frame.grid(row=6, column=0, columnspan=2, sticky="ew", pady=(20, 0))
ctk.CTkButton(
button_frame,
text="保存设置",
height=40,
font=ctk.CTkFont(size=14),
command=self.save_settings
).pack(side="left", padx=5)
ctk.CTkButton(
button_frame,
text="恢复默认",
height=40,
font=ctk.CTkFont(size=14),
fg_color="transparent",
border_width=2,
command=self.reset_settings
).pack(side="left", padx=5)
def show_files(self):
"""显示文件页面"""
self.clear_content()
self.content_title.configure(text="文件管理")
self.select_nav_button(4)
ctk.CTkLabel(
self.content_container,
text="文件管理页面正在开发中...",
font=ctk.CTkFont(size=20)
).pack(expand=True)
def show_tools(self):
"""显示工具页面"""
self.clear_content()
self.content_title.configure(text="工具")
self.select_nav_button(5)
ctk.CTkLabel(
self.content_container,
text="工具页面正在开发中...",
font=ctk.CTkFont(size=20)
).pack(expand=True)
def clear_content(self):
"""清空内容区域"""
for widget in self.content_container.winfo_children():
widget.destroy()
def select_nav_button(self, index):
"""选择导航按钮"""
for i, btn in enumerate(self.nav_buttons):
if i == index:
btn.configure(fg_color=("gray75", "gray25"))
else:
btn.configure(fg_color="transparent")
def update_ui(self):
"""更新UI"""
# 更新时间
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.time_label.configure(text=current_time)
# 更新图表
if hasattr(self, 'chart_canvas'):
self.draw_chart()
# 每隔1秒更新一次
self.after(1000, self.update_ui)
def on_search(self):
"""搜索事件"""
query = self.search_entry.get()
if query:
self.status_label.configure(text=f"搜索: {query}")
def show_notifications(self):
"""显示通知"""
messagebox.showinfo("通知", "您有3条新通知")
def show_user_menu(self):
"""显示用户菜单"""
# 创建菜单
menu = ctk.CTk()
menu.title("用户菜单")
menu.geometry("200x300")
ctk.CTkLabel(
menu,
text="用户菜单",
font=ctk.CTkFont(size=16, weight="bold")
).pack(pady=20)
options = ["个人资料", "账户设置", "隐私设置", "退出登录"]
for option in options:
ctk.CTkButton(
menu,
text=option,
command=lambda o=option: self.user_menu_action(o, menu)
).pack(pady=5, padx=20, fill="x")
menu.mainloop()
def user_menu_action(self, option, menu):
"""用户菜单动作"""
menu.destroy()
self.status_label.configure(text=f"选择了: {option}")
def toggle_theme(self):
"""切换主题"""
current_mode = ctk.get_appearance_mode()
if current_mode == "dark":
ctk.set_appearance_mode("light")
else:
ctk.set_appearance_mode("dark")
def change_appearance_mode(self):
"""更改外观模式"""
ctk.set_appearance_mode(self.theme_var.get())
def on_exit(self):
"""退出应用"""
if messagebox.askyesno("退出", "确定要退出应用吗?"):
self.destroy()
def add_user(self):
"""添加用户"""
dialog = ctk.CTkInputDialog(text="请输入用户名:", title="添加用户")
result = dialog.get_input()
if result:
new_id = max(user["id"] for user in self.users) + 1
self.users.append({
"id": new_id,
"name": result,
"email": f"{result.lower()}@example.com",
"role": "用户",
"status": "在线",
"avatar": "👤"
})
self.status_label.configure(text=f"已添加用户: {result}")
self.show_users() # 刷新用户页面
def edit_user(self):
"""编辑用户"""
self.status_label.configure(text="编辑用户功能")
def edit_user_action(self, user_id):
"""编辑用户动作"""
self.status_label.configure(text=f"编辑用户ID: {user_id}")
def delete_user(self):
"""删除用户"""
self.status_label.configure(text="删除用户功能")
def delete_user_action(self, user_id):
"""删除用户动作"""
if messagebox.askyesno("确认删除", f"确定要删除用户ID: {user_id}吗?"):
self.users = [user for user in self.users if user["id"] != user_id]
self.status_label.configure(text=f"已删除用户ID: {user_id}")
self.show_users() # 刷新用户页面
def refresh_users(self):
"""刷新用户列表"""
self.show_users()
def search_users(self):
"""搜索用户"""
query = self.user_search.get()
if query:
self.status_label.configure(text=f"搜索用户: {query}")
def save_settings(self):
"""保存设置"""
messagebox.showinfo("设置", "设置已保存")
self.status_label.configure(text="设置已保存")
def reset_settings(self):
"""恢复默认设置"""
if messagebox.askyesno("确认", "确定要恢复默认设置吗?"):
self.status_label.configure(text="设置已恢复默认")
def show_help(self):
"""显示帮助"""
messagebox.showinfo("帮助", "这是帮助信息")
def run(self):
"""运行应用"""
self.mainloop()
if __name__ == "__main__":
app = ModernApp()
app.run()
3.2.3 sv_ttk:Windows 11风格的现代主题
核心特点:
-
精确仿照Windows 11的Fluent Design设计语言
-
原生支持亮色/暗色模式
-
提供圆角、阴影等现代化视觉效果
-
完全兼容标准ttk控件,无需学习新API
-
轻量级,仅需几行代码即可启用
安装与使用:
bash
pip install sv-ttk
示例代码:创建Windows 11风格的应用
python
"""
sv_ttk完整示例
展示Windows 11风格的界面设计
"""
import tkinter as tk
from tkinter import ttk, messagebox
import sv_ttk
import json
from datetime import datetime
import random
class Windows11App:
"""Windows 11风格应用"""
def __init__(self):
self.root = tk.Tk()
self.root.title("Windows 11风格应用")
self.root.geometry("1200x800")
# 应用sv_ttk主题
sv_ttk.set_theme("dark") # 可选: "dark", "light"
# 初始化数据
self.init_data()
# 创建界面
self.create_ui()
# 启动定时器
self.update_clock()
def init_data(self):
"""初始化数据"""
self.tasks = [
{"id": 1, "title": "完成项目报告", "priority": "高", "status": "进行中", "due": "2024-12-31"},
{"id": 2, "title": "准备会议材料", "priority": "中", "status": "未开始", "due": "2024-12-20"},
{"id": 3, "title": "修复BUG", "priority": "高", "status": "进行中", "due": "2024-12-15"},
{"id": 4, "title": "代码审查", "priority": "低", "status": "已完成", "due": "2024-12-10"},
{"id": 5, "title": "团队培训", "priority": "中", "status": "未开始", "due": "2024-12-25"}
]
self.notifications = [
{"id": 1, "title": "系统更新", "message": "有可用的系统更新", "time": "10分钟前", "read": False},
{"id": 2, "title": "新消息", "message": "您收到一条新消息", "time": "30分钟前", "read": False},
{"id": 3, "title": "备份完成", "message": "系统备份已完成", "time": "1小时前", "read": True},
{"id": 4, "title": "磁盘空间", "message": "磁盘空间不足", "time": "2小时前", "read": False}
]
def create_ui(self):
"""创建用户界面"""
# 创建网格布局
self.root.grid_columnconfigure(1, weight=1)
self.root.grid_rowconfigure(0, weight=1)
# 1. 左侧导航栏
self.create_sidebar()
# 2. 主内容区域
self.create_main_content()
# 3. 右侧面板
self.create_right_panel()
def create_sidebar(self):
"""创建左侧导航栏"""
sidebar = ttk.Frame(self.root, width=200, style="Card")
sidebar.grid(row=0, column=0, sticky="nsew", padx=(10, 5), pady=10)
sidebar.grid_propagate(False)
# Logo区域
logo_frame = ttk.Frame(sidebar, style="Card")
logo_frame.pack(fill="x", padx=10, pady=(20, 20))
ttk.Label(
logo_frame,
text="📊",
font=("Segoe UI", 24)
).pack()
ttk.Label(
logo_frame,
text="任务看板",
font=("Segoe UI", 16, "bold")
).pack(pady=(5, 0))
# 导航按钮
nav_items = [
("🏠", "仪表盘", self.show_dashboard),
("📋", "任务", self.show_tasks),
("📅", "日历", self.show_calendar),
("📈", "统计", self.show_stats),
("⚙️", "设置", self.show_settings)
]
for icon, text, command in nav_items:
btn = ttk.Button(
sidebar,
text=f" {icon} {text}",
style="Accent.TButton" if text == "任务" else "TButton",
command=command
)
btn.pack(fill="x", padx=10, pady=5)
# 底部区域
bottom_frame = ttk.Frame(sidebar, style="Card")
bottom_frame.pack(side="bottom", fill="x", padx=10, pady=20)
ttk.Button(
bottom_frame,
text="❓ 帮助",
style="TButton",
command=self.show_help
).pack(fill="x", pady=5)
ttk.Button(
bottom_frame,
text="🚪 退出",
style="TButton",
command=self.root.quit
).pack(fill="x", pady=5)
def create_main_content(self):
"""创建主内容区域"""
# 主内容框架
main_content = ttk.Frame(self.root, style="Card")
main_content.grid(row=0, column=1, sticky="nsew", padx=5, pady=10)
main_content.grid_columnconfigure(0, weight=1)
main_content.grid_rowconfigure(1, weight=1)
# 标题栏
title_bar = ttk.Frame(main_content, style="Card")
title_bar.grid(row=0, column=0, sticky="ew", padx=20, pady=20)
title_bar.grid_columnconfigure(0, weight=1)
self.content_title = ttk.Label(
title_bar,
text="任务管理",
font=("Segoe UI", 20, "bold")
)
self.content_title.grid(row=0, column=0, sticky="w")
# 主题切换按钮
ttk.Button(
title_bar,
text="🌓 切换主题",
style="TButton",
command=self.toggle_theme
).grid(row=0, column=1, sticky="e", padx=(0, 10))
# 刷新按钮
ttk.Button(
title_bar,
text="🔄 刷新",
style="TButton",
command=self.refresh_data
).grid(row=0, column=2, sticky="e")
# 内容区域
self.content_area = ttk.Frame(main_content, style="Card")
self.content_area.grid(row=1, column=0, sticky="nsew", padx=20, pady=(0, 20))
# 显示默认页面
self.show_tasks()
def create_right_panel(self):
"""创建右侧面板"""
right_panel = ttk.Frame(self.root, width=300, style="Card")
right_panel.grid(row=0, column=2, sticky="nsew", padx=(5, 10), pady=10)
right_panel.grid_propagate(False)
# 通知标题
ttk.Label(
right_panel,
text="🔔 通知",
font=("Segoe UI", 16, "bold")
).pack(anchor="w", padx=20, pady=(20, 10))
# 通知列表
notification_frame = ttk.Frame(right_panel, style="Card")
notification_frame.pack(fill="both", expand=True, padx=20, pady=(0, 20))
# 创建通知项
for notification in self.notifications:
self.create_notification_item(notification_frame, notification)
# 系统信息
info_frame = ttk.Frame(right_panel, style="Card")
info_frame.pack(fill="x", padx=20, pady=(0, 20))
ttk.Label(
info_frame,
text="🖥️ 系统信息",
font=("Segoe UI", 14, "bold")
).pack(anchor="w", pady=(0, 10))
# CPU使用率
cpu_frame = ttk.Frame(info_frame, style="Card")
cpu_frame.pack(fill="x", pady=5)
ttk.Label(cpu_frame, text="CPU:").pack(side="left")
cpu_progress = ttk.Progressbar(
cpu_frame,
length=200,
mode="determinate",
value=random.randint(20, 80)
)
cpu_progress.pack(side="right", padx=(10, 0))
# 内存使用率
mem_frame = ttk.Frame(info_frame, style="Card")
mem_frame.pack(fill="x", pady=5)
ttk.Label(mem_frame, text="内存:").pack(side="left")
mem_progress = ttk.Progressbar(
mem_frame,
length=200,
mode="determinate",
value=random.randint(30, 90)
)
mem_progress.pack(side="right", padx=(10, 0))
# 磁盘使用率
disk_frame = ttk.Frame(info_frame, style="Card")
disk_frame.pack(fill="x", pady=5)
ttk.Label(disk_frame, text="磁盘:").pack(side="left")
disk_progress = ttk.Progressbar(
disk_frame,
length=200,
mode="determinate",
value=random.randint(40, 95)
)
disk_progress.pack(side="right", padx=(10, 0))
def create_notification_item(self, parent, notification):
"""创建通知项"""
item_frame = ttk.Frame(parent, style="Card")
item_frame.pack(fill="x", pady=5)
# 标题
title_frame = ttk.Frame(item_frame, style="Card")
title_frame.pack(fill="x", pady=(5, 2))
ttk.Label(
title_frame,
text=notification["title"],
font=("Segoe UI", 10, "bold"),
foreground="#0078D4" if not notification["read"] else "gray"
).pack(side="left")
ttk.Label(
title_frame,
text=notification["time"],
font=("Segoe UI", 8),
foreground="gray"
).pack(side="right")
# 消息
ttk.Label(
item_frame,
text=notification["message"],
font=("Segoe UI", 9),
wraplength=250
).pack(anchor="w", pady=(0, 5))
# 分隔线
ttk.Separator(item_frame).pack(fill="x", pady=5)
def show_dashboard(self):
"""显示仪表盘"""
self.clear_content()
self.content_title.configure(text="仪表盘")
ttk.Label(
self.content_area,
text="仪表盘页面正在开发中...",
font=("Segoe UI", 16)
).pack(expand=True)
def show_tasks(self):
"""显示任务页面"""
self.clear_content()
self.content_title.configure(text="任务管理")
# 创建任务工具栏
self.create_task_toolbar()
# 创建任务表格
self.create_task_table()
def create_task_toolbar(self):
"""创建任务工具栏"""
toolbar = ttk.Frame(self.content_area, style="Card")
toolbar.pack(fill="x", pady=(0, 20))
# 左侧按钮
left_frame = ttk.Frame(toolbar, style="Card")
left_frame.pack(side="left")
ttk.Button(
left_frame,
text="➕ 新建任务",
style="Accent.TButton",
command=self.create_task
).pack(side="left", padx=5)
ttk.Button(
left_frame,
text="✏️ 编辑",
style="TButton",
command=self.edit_task
).pack(side="left", padx=5)
ttk.Button(
left_frame,
text="🗑️ 删除",
style="TButton",
command=self.delete_task
).pack(side="left", padx=5)
# 右侧筛选
right_frame = ttk.Frame(toolbar, style="Card")
right_frame.pack(side="right")
ttk.Label(right_frame, text="状态筛选:").pack(side="left", padx=(0, 5))
self.filter_var = tk.StringVar(value="全部")
filter_combo = ttk.Combobox(
right_frame,
textvariable=self.filter_var,
values=["全部", "未开始", "进行中", "已完成"],
state="readonly",
width=10
)
filter_combo.pack(side="left", padx=(0, 10))
filter_combo.bind("<<ComboboxSelected>>", self.filter_tasks)
def create_task_table(self):
"""创建任务表格"""
# 创建表格框架
table_frame = ttk.Frame(self.content_area, style="Card")
table_frame.pack(fill="both", expand=True)
# 创建Treeview
columns = ("id", "title", "priority", "status", "due")
self.task_tree = ttk.Treeview(
table_frame,
columns=columns,
show="headings",
height=15
)
# 设置列标题
self.task_tree.heading("id", text="ID")
self.task_tree.heading("title", text="任务标题")
self.task_tree.heading("priority", text="优先级")
self.task_tree.heading("status", text="状态")
self.task_tree.heading("due", text="截止日期")
# 设置列宽
self.task_tree.column("id", width=50)
self.task_tree.column("title", width=300)
self.task_tree.column("priority", width=100)
self.task_tree.column("status", width=100)
self.task_tree.column("due", width=100)
# 添加数据
for task in self.tasks:
self.task_tree.insert("", "end", values=(
task["id"],
task["title"],
task["priority"],
task["status"],
task["due"]
))
# 添加滚动条
scrollbar = ttk.Scrollbar(table_frame, orient="vertical", command=self.task_tree.yview)
self.task_tree.configure(yscrollcommand=scrollbar.set)
self.task_tree.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# 绑定选择事件
self.task_tree.bind("<<TreeviewSelect>>", self.on_task_select)
def show_calendar(self):
"""显示日历"""
self.clear_content()
self.content_title.configure(text="日历")
ttk.Label(
self.content_area,
text="日历页面正在开发中...",
font=("Segoe UI", 16)
).pack(expand=True)
def show_stats(self):
"""显示统计"""
self.clear_content()
self.content_title.configure(text="统计")
ttk.Label(
self.content_area,
text="统计页面正在开发中...",
font=("Segoe UI", 16)
).pack(expand=True)
def show_settings(self):
"""显示设置"""
self.clear_content()
self.content_title.configure(text="设置")
# 创建设置表单
self.create_settings_form()
def create_settings_form(self):
"""创建设置表单"""
settings_frame = ttk.Frame(self.content_area, style="Card")
settings_frame.pack(fill="both", expand=True, padx=20, pady=20)
# 主题设置
ttk.Label(
settings_frame,
text="主题设置",
font=("Segoe UI", 16, "bold")
).grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 20))
ttk.Label(settings_frame, text="主题模式:").grid(row=1, column=0, sticky="w", pady=10)
self.theme_var = tk.StringVar(value="dark")
ttk.Radiobutton(
settings_frame,
text="深色模式",
variable=self.theme_var,
value="dark"
).grid(row=1, column=1, sticky="w", padx=20, pady=10)
ttk.Radiobutton(
settings_frame,
text="浅色模式",
variable=self.theme_var,
value="light"
).grid(row=2, column=1, sticky="w", padx=20, pady=10)
# 自动保存
self.auto_save_var = tk.BooleanVar(value=True)
ttk.Checkbutton(
settings_frame,
text="启用自动保存",
variable=self.auto_save_var
).grid(row=3, column=0, columnspan=2, sticky="w", pady=10)
# 通知
self.notify_var = tk.BooleanVar(value=True)
ttk.Checkbutton(
settings_frame,
text="启用通知",
variable=self.notify_var
).grid(row=4, column=0, columnspan=2, sticky="w", pady=10)
# 按钮
button_frame = ttk.Frame(settings_frame, style="Card")
button_frame.grid(row=5, column=0, columnspan=2, sticky="ew", pady=(20, 0))
ttk.Button(
button_frame,
text="保存设置",
style="Accent.TButton",
command=self.save_settings
).pack(side="left", padx=5)
ttk.Button(
button_frame,
text="恢复默认",
style="TButton",
command=self.reset_settings
).pack(side="left", padx=5)
def clear_content(self):
"""清空内容区域"""
for widget in self.content_area.winfo_children():
widget.destroy()
def update_clock(self):
"""更新时钟"""
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 可以在这里更新时间显示
self.root.after(1000, self.update_clock)
def toggle_theme(self):
"""切换主题"""
current_theme = sv_ttk.get_theme()
new_theme = "light" if current_theme == "dark" else "dark"
sv_ttk.set_theme(new_theme)
def refresh_data(self):
"""刷新数据"""
messagebox.showinfo("刷新", "数据已刷新")
def show_help(self):
"""显示帮助"""
messagebox.showinfo("帮助", "这是帮助信息")
def create_task(self):
"""创建任务"""
dialog = tk.Toplevel(self.root)
dialog.title("新建任务")
dialog.geometry("400x300")
dialog.transient(self.root)
dialog.grab_set()
ttk.Label(dialog, text="新建任务", font=("Segoe UI", 16, "bold")).pack(pady=20)
# 任务标题
ttk.Label(dialog, text="任务标题:").pack(anchor="w", padx=20, pady=(0, 5))
title_entry = ttk.Entry(dialog, width=40)
title_entry.pack(padx=20, pady=(0, 10))
# 优先级
ttk.Label(dialog, text="优先级:").pack(anchor="w", padx=20, pady=(0, 5))
priority_var = tk.StringVar(value="中")
priority_combo = ttk.Combobox(
dialog,
textvariable=priority_var,
values=["高", "中", "低"],
state="readonly",
width=10
)
priority_combo.pack(anchor="w", padx=20, pady=(0, 10))
# 截止日期
ttk.Label(dialog, text="截止日期:").pack(anchor="w", padx=20, pady=(0, 5))
due_entry = ttk.Entry(dialog, width=20)
due_entry.pack(anchor="w", padx=20, pady=(0, 20))
# 按钮
button_frame = ttk.Frame(dialog)
button_frame.pack(fill="x", padx=20, pady=20)
ttk.Button(
button_frame,
text="取消",
style="TButton",
command=dialog.destroy
).pack(side="right", padx=5)
ttk.Button(
button_frame,
text="确定",
style="Accent.TButton",
command=lambda: self.save_new_task(
dialog, title_entry.get(), priority_var.get(), due_entry.get()
)
).pack(side="right", padx=5)
def save_new_task(self, dialog, title, priority, due):
"""保存新任务"""
if title:
new_id = max(task["id"] for task in self.tasks) + 1
self.tasks.append({
"id": new_id,
"title": title,
"priority": priority,
"status": "未开始",
"due": due or "2024-12-31"
})
dialog.destroy()
self.show_tasks() # 刷新任务列表
messagebox.showinfo("成功", "任务已创建")
def edit_task(self):
"""编辑任务"""
selection = self.task_tree.selection()
if not selection:
messagebox.showwarning("提示", "请先选择要编辑的任务")
return
task_id = self.task_tree.item(selection[0], "values")[0]
messagebox.showinfo("编辑", f"编辑任务ID: {task_id}")
def delete_task(self):
"""删除任务"""
selection = self.task_tree.selection()
if not selection:
messagebox.showwarning("提示", "请先选择要删除的任务")
return
task_id = self.task_tree.item(selection[0], "values")[0]
if messagebox.askyesno("确认", f"确定要删除任务ID: {task_id}吗?"):
self.tasks = [task for task in self.tasks if task["id"] != int(task_id)]
self.show_tasks() # 刷新任务列表
def on_task_select(self, event):
"""任务选择事件"""
selection = self.task_tree.selection()
if selection:
task_id = self.task_tree.item(selection[0], "values")[0]
# 可以在这里显示任务详情
def filter_tasks(self, event):
"""筛选任务"""
filter_value = self.filter_var.get()
if filter_value == "全部":
# 显示所有任务
for item in self.task_tree.get_children():
self.task_tree.item(item, tags=())
else:
# 只显示匹配状态的任务
for item in self.task_tree.get_children():
task_status = self.task_tree.item(item, "values")[3]
if task_status == filter_value:
self.task_tree.item(item, tags=())
else:
self.task_tree.item(item, tags=("hidden",))
self.task_tree.tag_configure("hidden", foreground="gray")
def save_settings(self):
"""保存设置"""
# 应用主题
sv_ttk.set_theme(self.theme_var.get())
messagebox.showinfo("设置", "设置已保存")
def reset_settings(self):
"""恢复默认设置"""
if messagebox.askyesno("确认", "确定要恢复默认设置吗?"):
self.theme_var.set("dark")
self.auto_save_var.set(True)
self.notify_var.set(True)
messagebox.showinfo("设置", "设置已恢复默认")
def run(self):
"""运行应用"""
self.root.mainloop()
if __name__ == "__main__":
app = Windows11App()
app.run()
3.3 第三方主题库对比总结
第三方主题库特性对比

对比分析表:
| 特性 | ttkbootstrap | customtkinter | sv_ttk | 原生ttk |
|---|---|---|---|---|
| 设计风格 | Bootstrap 5风格 | 完全自定义现代化风格 | Windows 11风格 | 操作系统原生风格 |
| 主题支持 | 完整亮色/暗色主题 | 完整亮色/暗色主题 | 完整亮色/暗色主题 | 有限主题支持 |
| 控件丰富度 | 非常丰富(含高级控件) | 非常丰富(含高级控件) | 标准ttk控件扩展 | 基础控件 |
| 自定义程度 | 高(基于样式系统) | 非常高(完全自定义) | 中等(基于主题) | 低 |
| 性能表现 | 优秀 | 良好(稍重) | 优秀 | 优秀 |
| 学习曲线 | 平缓(Bootstrap风格) | 中等(新API) | 平缓(标准ttk) | 平缓 |
| 文档完整性 | 优秀 | 良好 | 良好 | 良好 |
| 社区活跃度 | 高 | 高 | 中等 | 高 |
| 适用场景 | 企业应用、Web风格应用 | 现代化桌面应用、高自定义需求 | Windows平台应用、原生体验 | 基础应用、快速原型 |
| 依赖大小 | 中等 | 较大 | 小 | 无(Python标准库) |
选择建议:
-
选择ttkbootstrap如果:
-
你熟悉Bootstrap框架
-
需要快速构建Web风格的现代化界面
-
需要丰富的预定义组件和样式
-
项目需要亮色/暗色主题切换
-
-
选择customtkinter如果:
-
需要完全自定义的现代化界面
-
不满足于操作系统原生样式
-
需要创建独特的品牌视觉风格
-
可以接受稍重的依赖和性能开销
-
-
选择sv_ttk如果:
-
目标是Windows平台应用
-
需要Windows 11的现代化外观
-
希望保持标准ttk API的兼容性
-
需要轻量级的解决方案
-
-
选择原生ttk如果:
-
应用对性能要求极高
-
需要最小的依赖和部署大小
-
界面简洁即可满足需求
-
需要最大程度的兼容性
-
3.4 实战:构建现代化主题切换系统
python
"""
现代化主题切换系统
演示如何构建完整的主题切换机制
支持多种主题库:ttk、ttkbootstrap、customtkinter、sv_ttk
"""
import tkinter as tk
from tkinter import ttk, messagebox
import json
import os
from pathlib import Path
from typing import Dict, Any, Optional, List, Tuple, Callable
import ttkbootstrap as tb
from ttkbootstrap.constants import *
import customtkinter as ctk
import sv_ttk
import sys
class ThemeSwitcher:
"""主题切换器 - 支持多种主题库"""
def __init__(self):
self.themes: Dict[str, Dict[str, Any]] = {}
self.current_theme: str = "default"
self.theme_type: str = "ttk" # ttk, ttkbootstrap, customtkinter, sv_ttk
self.root_window: Optional[tk.Tk] = None
self.theme_change_callbacks: List[Callable] = []
# 加载主题配置
self.load_themes()
def load_themes(self):
"""加载主题配置"""
# 基础主题
self.themes = {
"default": {
"name": "默认主题",
"type": "ttk",
"style": "clam",
"description": "标准Tkinter主题,轻量级"
},
"dark": {
"name": "深色主题",
"type": "ttk",
"style": "clam",
"description": "深色模式标准主题",
"colors": {
"bg": "#2b2b2b",
"fg": "#ffffff",
"accent": "#007acc"
}
},
"ttkbootstrap_light": {
"name": "Bootstrap浅色",
"type": "ttkbootstrap",
"theme": "flatly",
"description": "Bootstrap风格的浅色主题"
},
"ttkbootstrap_dark": {
"name": "Bootstrap深色",
"type": "ttkbootstrap",
"theme": "superhero",
"description": "Bootstrap风格的深色主题"
},
"customtkinter_blue": {
"name": "CTK蓝色主题",
"type": "customtkinter",
"appearance": "dark",
"color_theme": "blue",
"description": "CustomTkinter蓝色主题"
},
"customtkinter_green": {
"name": "CTK绿色主题",
"type": "customtkinter",
"appearance": "dark",
"color_theme": "green",
"description": "CustomTkinter绿色主题"
},
"sv_ttk_light": {
"name": "Windows浅色",
"type": "sv_ttk",
"theme": "light",
"description": "Windows 11风格的浅色主题"
},
"sv_ttk_dark": {
"name": "Windows深色",
"type": "sv_ttk",
"theme": "dark",
"description": "Windows 11风格的深色主题"
}
}
# 从文件加载自定义主题
self.load_custom_themes()
def load_custom_themes(self):
"""从文件加载自定义主题"""
config_file = Path("themes.json")
if config_file.exists():
try:
with open(config_file, 'r', encoding='utf-8') as f:
custom_themes = json.load(f)
for theme_id, theme_config in custom_themes.items():
self.themes[theme_id] = theme_config
print(f"✅ 已加载 {len(custom_themes)} 个自定义主题")
except Exception as e:
print(f"❌ 加载自定义主题失败: {e}")
def save_custom_themes(self):
"""保存自定义主题到文件"""
try:
# 只保存自定义主题
custom_themes = {}
for theme_id, theme_config in self.themes.items():
if theme_id not in ["default", "dark", "ttkbootstrap_light",
"ttkbootstrap_dark", "customtkinter_blue",
"customtkinter_green", "sv_ttk_light", "sv_ttk_dark"]:
custom_themes[theme_id] = theme_config
config_file = Path("themes.json")
with open(config_file, 'w', encoding='utf-8') as f:
json.dump(custom_themes, f, indent=2, ensure_ascii=False)
print(f"✅ 已保存 {len(custom_themes)} 个自定义主题")
return True
except Exception as e:
print(f"❌ 保存自定义主题失败: {e}")
return False
def add_custom_theme(self, theme_id: str, theme_config: Dict[str, Any]) -> bool:
"""添加自定义主题"""
if theme_id in self.themes:
print(f"⚠️ 主题 '{theme_id}' 已存在,将被覆盖")
# 验证主题配置
required_fields = ["name", "type", "description"]
for field in required_fields:
if field not in theme_config:
print(f"❌ 主题配置缺少必要字段: {field}")
return False
self.themes[theme_id] = theme_config
# 保存到文件
self.save_custom_themes()
print(f"✅ 已添加自定义主题: {theme_config['name']}")
return True
def remove_custom_theme(self, theme_id: str) -> bool:
"""移除自定义主题"""
if theme_id in ["default", "dark", "ttkbootstrap_light", "ttkbootstrap_dark",
"customtkinter_blue", "customtkinter_green", "sv_ttk_light", "sv_ttk_dark"]:
print(f"❌ 不能删除内置主题: {theme_id}")
return False
if theme_id in self.themes:
theme_name = self.themes[theme_id]["name"]
del self.themes[theme_id]
# 保存到文件
self.save_custom_themes()
print(f"✅ 已删除自定义主题: {theme_name}")
return True
print(f"❌ 主题不存在: {theme_id}")
return False
def get_theme_info(self, theme_id: str) -> Optional[Dict[str, Any]]:
"""获取主题信息"""
return self.themes.get(theme_id)
def get_theme_list(self) -> List[Tuple[str, str]]:
"""获取主题列表"""
theme_list = []
for theme_id, theme_config in self.themes.items():
theme_list.append((theme_id, theme_config["name"]))
return theme_list
def get_theme_list_by_type(self, theme_type: str) -> List[Tuple[str, str]]:
"""按类型获取主题列表"""
theme_list = []
for theme_id, theme_config in self.themes.items():
if theme_config["type"] == theme_type:
theme_list.append((theme_id, theme_config["name"]))
return theme_list
def set_root_window(self, root: tk.Tk):
"""设置根窗口"""
self.root_window = root
def register_callback(self, callback: Callable):
"""注册主题变化回调函数"""
self.theme_change_callbacks.append(callback)
def apply_theme(self, theme_id: str) -> bool:
"""应用主题"""
if theme_id not in self.themes:
print(f"❌ 主题不存在: {theme_id}")
return False
theme_config = self.themes[theme_id]
theme_type = theme_config["type"]
try:
if theme_type == "ttk":
success = self.apply_ttk_theme(theme_config)
elif theme_type == "ttkbootstrap":
success = self.apply_ttkbootstrap_theme(theme_config)
elif theme_type == "customtkinter":
success = self.apply_customtkinter_theme(theme_config)
elif theme_type == "sv_ttk":
success = self.apply_sv_ttk_theme(theme_config)
else:
print(f"❌ 不支持的主题类型: {theme_type}")
return False
if success:
self.current_theme = theme_id
self.theme_type = theme_type
# 调用回调函数
for callback in self.theme_change_callbacks:
try:
callback(theme_id, theme_config)
except Exception as e:
print(f"❌ 主题变化回调失败: {e}")
print(f"✅ 已应用主题: {theme_config['name']}")
return True
else:
return False
except Exception as e:
print(f"❌ 应用主题失败: {e}")
return False
def apply_ttk_theme(self, theme_config: Dict[str, Any]) -> bool:
"""应用标准ttk主题"""
if not self.root_window:
print("❌ 未设置根窗口")
return False
try:
# 获取样式对象
style = ttk.Style()
# 设置主题
theme_name = theme_config.get("style", "clam")
try:
style.theme_use(theme_name)
except:
print(f"⚠️ 主题 {theme_name} 不可用,使用默认主题")
style.theme_use("clam")
# 如果是深色主题,配置深色样式
if "dark" in theme_config.get("name", "").lower() or "dark" in theme_config.get("theme", ""):
self.configure_dark_ttk_theme(style, theme_config)
return True
except Exception as e:
print(f"❌ 应用ttk主题失败: {e}")
return False
def configure_dark_ttk_theme(self, style: ttk.Style, theme_config: Dict[str, Any]):
"""配置深色ttk主题"""
colors = theme_config.get("colors", {})
bg_color = colors.get("bg", "#2b2b2b")
fg_color = colors.get("fg", "#ffffff")
accent_color = colors.get("accent", "#007acc")
# 配置基本样式
style.configure(".", background=bg_color, foreground=fg_color)
# 配置特定控件样式
style.configure("TLabel", background=bg_color, foreground=fg_color)
style.configure("TButton", background=accent_color, foreground=fg_color)
style.configure("TEntry", fieldbackground=bg_color, foreground=fg_color)
style.configure("TFrame", background=bg_color)
style.configure("TLabelframe", background=bg_color, foreground=fg_color)
style.configure("TCheckbutton", background=bg_color, foreground=fg_color)
style.configure("TRadiobutton", background=bg_color, foreground=fg_color)
style.configure("TScale", background=bg_color, foreground=fg_color)
style.configure("Horizontal.TProgressbar", background=accent_color)
# 映射按钮状态
style.map("TButton",
background=[("active", self.lighten_color(accent_color, 20)),
("disabled", "#666666")])
def apply_ttkbootstrap_theme(self, theme_config: Dict[str, Any]) -> bool:
"""应用ttkbootstrap主题"""
if not self.root_window:
print("❌ 未设置根窗口")
return False
try:
# 获取主题名称
theme_name = theme_config.get("theme", "flatly")
# 检查ttkbootstrap是否已初始化
if not hasattr(self.root_window, 'style'):
print("❌ ttkbootstrap未初始化")
return False
# 应用主题
self.root_window.style.theme_use(theme_name)
return True
except Exception as e:
print(f"❌ 应用ttkbootstrap主题失败: {e}")
return False
def apply_customtkinter_theme(self, theme_config: Dict[str, Any]) -> bool:
"""应用customtkinter主题"""
try:
# 设置外观模式
appearance = theme_config.get("appearance", "dark")
ctk.set_appearance_mode(appearance)
# 设置颜色主题
color_theme = theme_config.get("color_theme", "blue")
ctk.set_default_color_theme(color_theme)
return True
except Exception as e:
print(f"❌ 应用customtkinter主题失败: {e}")
return False
def apply_sv_ttk_theme(self, theme_config: Dict[str, Any]) -> bool:
"""应用sv_ttk主题"""
try:
# 获取主题名称
theme_name = theme_config.get("theme", "dark")
# 应用主题
sv_ttk.set_theme(theme_name)
return True
except Exception as e:
print(f"❌ 应用sv_ttk主题失败: {e}")
return False
def lighten_color(self, hex_color: str, amount: int) -> str:
"""加亮颜色"""
# 简化实现
if hex_color.startswith("#"):
hex_color = hex_color[1:]
if len(hex_color) == 6:
r = int(hex_color[0:2], 16)
g = int(hex_color[2:4], 16)
b = int(hex_color[4:6], 16)
r = min(255, r + amount)
g = min(255, g + amount)
b = min(255, b + amount)
return f"#{r:02x}{g:02x}{b:02x}"
return hex_color
def get_current_theme_info(self) -> Dict[str, Any]:
"""获取当前主题信息"""
return self.themes.get(self.current_theme, {})
class ThemeDemoApp:
"""主题演示应用程序"""
def __init__(self):
# 创建主窗口
self.root = tk.Tk()
self.root.title("现代化主题切换系统演示")
self.root.geometry("1200x800")
# 初始化主题切换器
self.theme_switcher = ThemeSwitcher()
self.theme_switcher.set_root_window(self.root)
# 注册主题变化回调
self.theme_switcher.register_callback(self.on_theme_changed)
# 创建界面
self.create_ui()
# 应用默认主题
self.apply_initial_theme()
# 设置窗口居中
self.center_window()
# 绑定窗口关闭事件
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
def create_ui(self):
"""创建用户界面"""
# 主容器
main_container = ttk.Frame(self.root)
main_container.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 配置网格
main_container.columnconfigure(1, weight=1)
main_container.rowconfigure(1, weight=1)
# 1. 顶部工具栏
self.create_toolbar(main_container)
# 2. 左侧主题面板
self.create_theme_panel(main_container)
# 3. 主内容区域
self.create_main_content(main_container)
# 4. 底部状态栏
self.create_statusbar(main_container)
def create_toolbar(self, parent):
"""创建工具栏"""
toolbar = ttk.Frame(parent, relief=tk.RAISED, borderwidth=1)
toolbar.grid(row=0, column=0, columnspan=2, sticky="ew", pady=(0, 10))
# 左侧:应用标题
left_frame = ttk.Frame(toolbar)
left_frame.pack(side=tk.LEFT, padx=10, pady=5)
ttk.Label(
left_frame,
text="🎨 主题切换系统",
font=("Segoe UI", 16, "bold")
).pack(side=tk.LEFT, padx=(0, 20))
# 右侧:工具按钮
right_frame = ttk.Frame(toolbar)
right_frame.pack(side=tk.RIGHT, padx=10, pady=5)
ttk.Button(
right_frame,
text="刷新",
command=self.refresh_ui
).pack(side=tk.LEFT, padx=2)
ttk.Button(
right_frame,
text="关于",
command=self.show_about
).pack(side=tk.LEFT, padx=2)
ttk.Button(
right_frame,
text="导出主题",
command=self.export_theme
).pack(side=tk.LEFT, padx=2)
def create_theme_panel(self, parent):
"""创建左侧主题面板"""
# 左侧面板
left_panel = ttk.LabelFrame(parent, text="主题管理", padding=10)
left_panel.grid(row=1, column=0, sticky="nsew", padx=(0, 10))
left_panel.grid_propagate(False)
left_panel.config(width=300)
# 主题类型筛选
type_frame = ttk.Frame(left_panel)
type_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Label(type_frame, text="筛选类型:").pack(side=tk.LEFT, padx=(0, 5))
self.filter_var = tk.StringVar(value="全部")
type_combo = ttk.Combobox(
type_frame,
textvariable=self.filter_var,
values=["全部", "ttk", "ttkbootstrap", "customtkinter", "sv_ttk"],
state="readonly",
width=12
)
type_combo.pack(side=tk.LEFT)
type_combo.bind("<<ComboboxSelected>>", self.filter_themes)
# 主题列表容器
list_container = ttk.Frame(left_panel)
list_container.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
# 创建滚动条
list_scrollbar = ttk.Scrollbar(list_container)
list_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 创建列表框
self.theme_listbox = tk.Listbox(
list_container,
yscrollcommand=list_scrollbar.set,
selectmode=tk.SINGLE,
font=("Segoe UI", 10)
)
self.theme_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
list_scrollbar.config(command=self.theme_listbox.yview)
# 绑定选择事件
self.theme_listbox.bind("<<ListboxSelect>>", self.on_theme_select)
# 初始填充主题列表
self.populate_theme_list()
# 主题操作按钮
button_frame = ttk.Frame(left_panel)
button_frame.pack(fill=tk.X)
ttk.Button(
button_frame,
text="应用主题",
command=self.apply_selected_theme
).pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True)
ttk.Button(
button_frame,
text="删除主题",
command=self.delete_selected_theme
).pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True)
def create_main_content(self, parent):
"""创建主内容区域"""
# 右侧主内容区域
right_panel = ttk.Frame(parent)
right_panel.grid(row=1, column=1, sticky="nsew")
right_panel.columnconfigure(0, weight=1)
right_panel.rowconfigure(1, weight=1)
# 主题详情面板
self.detail_panel = ttk.LabelFrame(right_panel, text="主题详情", padding=15)
self.detail_panel.grid(row=0, column=0, sticky="ew", pady=(0, 10))
# 默认详情
self.show_default_detail()
# 控件演示区域
demo_panel = ttk.LabelFrame(right_panel, text="控件演示", padding=15)
demo_panel.grid(row=1, column=0, sticky="nsew")
demo_panel.columnconfigure(0, weight=1)
demo_panel.rowconfigure(0, weight=1)
# 创建控件演示
self.create_widget_demo(demo_panel)
def create_widget_demo(self, parent):
"""创建控件演示"""
# 使用网格布局
parent.columnconfigure(0, weight=1)
parent.columnconfigure(1, weight=1)
parent.columnconfigure(2, weight=1)
# 行0:标签演示
label_frame = ttk.LabelFrame(parent, text="标签", padding=10)
label_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
ttk.Label(label_frame, text="普通标签").pack(pady=2)
ttk.Label(label_frame, text="重要标签", font=("Segoe UI", 10, "bold")).pack(pady=2)
ttk.Label(label_frame, text="链接标签", foreground="blue").pack(pady=2)
# 行0:按钮演示
button_frame = ttk.LabelFrame(parent, text="按钮", padding=10)
button_frame.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
ttk.Button(button_frame, text="普通按钮").pack(pady=2, fill=tk.X)
ttk.Button(button_frame, text="主要按钮").pack(pady=2, fill=tk.X)
ttk.Button(button_frame, text="禁用按钮", state=tk.DISABLED).pack(pady=2, fill=tk.X)
# 行0:输入框演示
input_frame = ttk.LabelFrame(parent, text="输入控件", padding=10)
input_frame.grid(row=0, column=2, padx=5, pady=5, sticky="nsew")
ttk.Label(input_frame, text="文本框:").pack(anchor=tk.W, pady=(0, 2))
ttk.Entry(input_frame).pack(fill=tk.X, pady=(0, 10))
ttk.Label(input_frame, text="组合框:").pack(anchor=tk.W, pady=(0, 2))
ttk.Combobox(input_frame, values=["选项1", "选项2", "选项3"]).pack(fill=tk.X)
# 行1:复选框和单选按钮
check_frame = ttk.LabelFrame(parent, text="选择控件", padding=10)
check_frame.grid(row=1, column=0, padx=5, pady=5, sticky="nsew")
ttk.Checkbutton(check_frame, text="复选框1").pack(anchor=tk.W, pady=2)
ttk.Checkbutton(check_frame, text="复选框2").pack(anchor=tk.W, pady=2)
radio_var = tk.StringVar(value="1")
ttk.Radiobutton(check_frame, text="单选按钮1", variable=radio_var, value="1").pack(anchor=tk.W, pady=2)
ttk.Radiobutton(check_frame, text="单选按钮2", variable=radio_var, value="2").pack(anchor=tk.W, pady=2)
# 行1:滑块和进度条
slider_frame = ttk.LabelFrame(parent, text="滑块和进度", padding=10)
slider_frame.grid(row=1, column=1, padx=5, pady=5, sticky="nsew")
ttk.Label(slider_frame, text="滑块:").pack(anchor=tk.W, pady=(0, 2))
ttk.Scale(slider_frame, from_=0, to=100, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=(0, 10))
ttk.Label(slider_frame, text="进度条:").pack(anchor=tk.W, pady=(0, 2))
ttk.Progressbar(slider_frame, mode="determinate", value=50).pack(fill=tk.X)
# 行1:树形视图
tree_frame = ttk.LabelFrame(parent, text="树形视图", padding=10)
tree_frame.grid(row=1, column=2, padx=5, pady=5, sticky="nsew")
# 创建简单的树
tree = ttk.Treeview(tree_frame, height=4, show="tree")
tree.pack(fill=tk.BOTH, expand=True)
# 添加示例节点
for i in range(3):
parent = tree.insert("", tk.END, text=f"父节点 {i+1}")
for j in range(2):
tree.insert(parent, tk.END, text=f"子节点 {j+1}")
def create_statusbar(self, parent):
"""创建状态栏"""
statusbar = ttk.Frame(parent, relief=tk.SUNKEN, borderwidth=1)
statusbar.grid(row=2, column=0, columnspan=2, sticky="ew", pady=(10, 0))
# 左侧状态
self.status_label = ttk.Label(
statusbar,
text="就绪 | 当前主题: 默认主题"
)
self.status_label.pack(side=tk.LEFT, padx=10, pady=2)
# 右侧信息
ttk.Label(
statusbar,
text="主题切换系统演示 v1.0"
).pack(side=tk.RIGHT, padx=10, pady=2)
def show_default_detail(self):
"""显示默认详情"""
for widget in self.detail_panel.winfo_children():
widget.destroy()
ttk.Label(
self.detail_panel,
text="请从左侧选择主题查看详情",
font=("Segoe UI", 12)
).pack(pady=20)
def show_theme_detail(self, theme_id: str, theme_config: Dict[str, Any]):
"""显示主题详情"""
for widget in self.detail_panel.winfo_children():
widget.destroy()
# 使用网格布局
self.detail_panel.columnconfigure(1, weight=1)
# 主题名称
ttk.Label(
self.detail_panel,
text=theme_config["name"],
font=("Segoe UI", 16, "bold")
).grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 10))
# 主题ID
ttk.Label(self.detail_panel, text="主题ID:", font=("Segoe UI", 10, "bold")).grid(row=1, column=0, sticky="w", pady=2)
ttk.Label(self.detail_panel, text=theme_id).grid(row=1, column=1, sticky="w", pady=2, padx=(10, 0))
# 主题类型
ttk.Label(self.detail_panel, text="主题类型:", font=("Segoe UI", 10, "bold")).grid(row=2, column=0, sticky="w", pady=2)
ttk.Label(self.detail_panel, text=theme_config["type"]).grid(row=2, column=1, sticky="w", pady=2, padx=(10, 0))
# 描述
ttk.Label(self.detail_panel, text="描述:", font=("Segoe UI", 10, "bold")).grid(row=3, column=0, sticky="nw", pady=2)
description = theme_config.get("description", "无描述")
ttk.Label(self.detail_panel, text=description, wraplength=400, justify=tk.LEFT).grid(
row=3, column=1, sticky="w", pady=2, padx=(10, 0)
)
# 配置详情
ttk.Label(self.detail_panel, text="配置详情:", font=("Segoe UI", 10, "bold")).grid(
row=4, column=0, sticky="nw", pady=(10, 2)
)
# 创建文本区域显示配置
config_text = tk.Text(self.detail_panel, height=8, width=50)
config_scrollbar = ttk.Scrollbar(self.detail_panel, orient=tk.VERTICAL, command=config_text.yview)
config_text.configure(yscrollcommand=config_scrollbar.set)
config_text.grid(row=4, column=1, sticky="nsew", pady=(10, 2), padx=(10, 0))
config_scrollbar.grid(row=4, column=2, sticky="ns", pady=(10, 2))
# 添加配置信息
config_str = json.dumps(theme_config, indent=2, ensure_ascii=False)
config_text.insert(tk.END, config_str)
config_text.config(state=tk.DISABLED)
def populate_theme_list(self, filter_type: str = "全部"):
"""填充主题列表"""
# 清空列表
self.theme_listbox.delete(0, tk.END)
# 获取主题列表
all_themes = self.theme_switcher.get_theme_list()
for theme_id, theme_name in all_themes:
theme_info = self.theme_switcher.get_theme_info(theme_id)
theme_type = theme_info.get("type", "unknown")
if filter_type == "全部" or theme_type == filter_type:
display_text = f"{theme_name} ({theme_type})"
self.theme_listbox.insert(tk.END, display_text)
# 存储主题ID
self.theme_listbox.items.append(theme_id)
def apply_initial_theme(self):
"""应用初始主题"""
# 获取系统主题偏好
import platform
system = platform.system()
if system == "Windows":
# 可以检查Windows主题设置
import ctypes
try:
# 检查Windows主题
value = ctypes.windll.user32.GetSysColor(15) # COLOR_WINDOW
# 简单判断是否为深色
is_dark = value < 0x888888
initial_theme = "dark" if is_dark else "default"
except:
initial_theme = "default"
elif system == "Darwin": # macOS
# macOS主题检测
initial_theme = "default"
else: # Linux
initial_theme = "default"
# 应用主题
self.theme_switcher.apply_theme(initial_theme)
self.update_status(initial_theme)
def center_window(self):
"""窗口居中显示"""
self.root.update_idletasks()
width = self.root.winfo_width()
height = self.root.winfo_height()
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
x = (screen_width - width) // 2
y = (screen_height - height) // 2
self.root.geometry(f"{width}x{height}+{x}+{y}")
def on_theme_select(self, event):
"""主题选择事件"""
selection = self.theme_listbox.curselection()
if selection:
index = selection[0]
theme_id = self.theme_listbox.items[index]
theme_config = self.theme_switcher.get_theme_info(theme_id)
if theme_config:
self.show_theme_detail(theme_id, theme_config)
def apply_selected_theme(self):
"""应用选中的主题"""
selection = self.theme_listbox.curselection()
if selection:
index = selection[0]
theme_id = self.theme_listbox.items[index]
# 应用主题
success = self.theme_switcher.apply_theme(theme_id)
if success:
theme_config = self.theme_switcher.get_theme_info(theme_id)
self.update_status(theme_id)
messagebox.showinfo("成功", f"已应用主题: {theme_config['name']}")
else:
messagebox.showerror("错误", "应用主题失败")
else:
messagebox.showwarning("提示", "请先选择一个主题")
def delete_selected_theme(self):
"""删除选中的主题"""
selection = self.theme_listbox.curselection()
if selection:
index = selection[0]
theme_id = self.theme_listbox.items[index]
# 确认删除
theme_config = self.theme_switcher.get_theme_info(theme_id)
if messagebox.askyesno("确认删除", f"确定要删除主题 '{theme_config['name']}' 吗?"):
# 删除主题
success = self.theme_switcher.remove_custom_theme(theme_id)
if success:
# 刷新列表
self.populate_theme_list(self.filter_var.get())
self.show_default_detail()
messagebox.showinfo("成功", "主题已删除")
else:
messagebox.showerror("错误", "删除主题失败")
else:
messagebox.showwarning("提示", "请先选择一个主题")
def filter_themes(self, event):
"""筛选主题"""
filter_type = self.filter_var.get()
self.populate_theme_list(filter_type)
def refresh_ui(self):
"""刷新UI"""
# 重新填充主题列表
self.populate_theme_list(self.filter_var.get())
# 更新状态
current_theme = self.theme_switcher.current_theme
self.update_status(current_theme)
messagebox.showinfo("刷新", "界面已刷新")
def update_status(self, theme_id: str):
"""更新状态栏"""
theme_config = self.theme_switcher.get_theme_info(theme_id)
theme_name = theme_config.get("name", "未知主题")
theme_type = theme_config.get("type", "未知")
self.status_label.config(text=f"当前主题: {theme_name} ({theme_type})")
def on_theme_changed(self, theme_id: str, theme_config: Dict[str, Any]):
"""主题变化回调"""
print(f"主题已切换: {theme_config['name']}")
self.update_status(theme_id)
def show_about(self):
"""显示关于对话框"""
about_dialog = tk.Toplevel(self.root)
about_dialog.title("关于")
about_dialog.geometry("400x300")
about_dialog.transient(self.root)
about_dialog.grab_set()
# 居中显示
about_dialog.update_idletasks()
width = about_dialog.winfo_width()
height = about_dialog.winfo_height()
parent_x = self.root.winfo_rootx()
parent_y = self.root.winfo_rooty()
parent_width = self.root.winfo_width()
parent_height = self.root.winfo_height()
x = parent_x + (parent_width - width) // 2
y = parent_y + (parent_height - height) // 2
about_dialog.geometry(f"{width}x{height}+{x}+{y}")
# 关于内容
ttk.Label(
about_dialog,
text="现代化主题切换系统",
font=("Segoe UI", 16, "bold")
).pack(pady=20)
ttk.Label(
about_dialog,
text="版本: 1.0.0\n\n"
"支持的主题库:\n"
"• 标准ttk主题\n"
"• ttkbootstrap (Bootstrap风格)\n"
"• customtkinter (高度自定义)\n"
"• sv_ttk (Windows 11风格)\n\n"
"作者: Python GUI开发者"
).pack(pady=10)
ttk.Button(
about_dialog,
text="关闭",
command=about_dialog.destroy
).pack(pady=20)
def export_theme(self):
"""导出主题"""
selection = self.theme_listbox.curselection()
if selection:
index = selection[0]
theme_id = self.theme_listbox.items[index]
# 获取主题配置
theme_config = self.theme_switcher.get_theme_info(theme_id)
# 弹出保存文件对话框
from tkinter import filedialog
file_path = filedialog.asksaveasfilename(
defaultextension=".json",
filetypes=[("JSON files", "*.json"), ("All files", "*.*")],
initialfile=f"{theme_id}_theme.json"
)
if file_path:
try:
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(theme_config, f, indent=2, ensure_ascii=False)
messagebox.showinfo("导出成功", f"主题已导出到:\n{file_path}")
except Exception as e:
messagebox.showerror("导出失败", f"导出主题失败:\n{str(e)}")
else:
messagebox.showwarning("提示", "请先选择一个主题")
def on_closing(self):
"""窗口关闭事件"""
if messagebox.askokcancel("退出", "确定要退出程序吗?"):
self.root.destroy()
def run(self):
"""运行应用程序"""
# 初始化列表项存储
self.theme_listbox.items = []
self.root.mainloop()
# 扩展Listbox以存储附加数据
class ExtendedListbox(tk.Listbox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.items = [] # 存储附加数据
if __name__ == "__main__":
app = ThemeDemoApp()
app.run()
第三部分总结
通过本部分的深入探讨,我们建立了完整的Tkinter/ttk现代化样式系统知识体系:
核心要点总结
-
ttk.Style机制是基础:
-
理解元素、样式、主题、状态映射的核心概念
-
掌握样式配置、映射和布局的完整API
-
学会创建自定义样式和主题
-
-
第三方主题库各有优势:
-
ttkbootstrap:适合快速构建Web风格应用
-
customtkinter:适合高度自定义的现代化应用
-
sv_ttk:适合追求Windows 11原生体验的应用
-
根据项目需求选择合适的主题库
-
-
主题切换系统是现代化应用的关键:
-
支持多种主题库的统一管理
-
提供灵活的主题配置和扩展
-
实现动态主题切换和无缝用户体验
-
设计原则提炼
-
一致性原则:在整个应用中保持统一的视觉风格
-
可配置性原则:提供灵活的主题配置选项
-
可扩展性原则:支持轻松添加新主题和主题库
-
用户体验原则:主题切换应平滑,不影响应用功能
最佳实践建议
-
项目初期确定主题策略:
-
根据目标用户选择主题风格
-
考虑应用的部署环境和平台
-
评估团队的技术栈熟悉度
-
-
实现主题抽象层:
-
封装主题相关代码,降低耦合
-
提供统一的主题管理接口
-
支持热切换主题而不重启应用
-
-
测试主题兼容性:
-
在不同平台测试主题表现
-
确保高对比度主题的可访问性
-
测试主题切换的性能影响
-
-
提供主题配置工具:
-
允许用户自定义颜色方案
-
提供主题预览功能
-
支持主题导入/导出
-
下一步建议
在实际项目中,建议按以下步骤实施主题系统:
-
需求分析:确定目标用户、平台和视觉风格需求
-
技术选型:根据需求选择合适的主题库
-
原型设计:创建多个主题方案进行对比
-
系统实现:构建灵活的主题管理架构
-
测试优化:全面测试主题兼容性和性能
-
用户反馈:收集用户反馈,持续优化主题体验
通过本部分的学习,您已经掌握了构建现代化Tkinter应用所需的所有样式和主题知识。第四部分将提供四个完整的实战Demo工程,将这些理论知识转化为实际可用的应用。