Files
pqAutomationApp/app/views/collapsing_frame.py

173 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""现代化的可折叠面板(取代 v1 的图标按钮版本)。
升级要点(保留 ``add(child, title=...)`` 旧签名兼容):
- header 整条可点击切换展开/收起;
- 使用 Unicode chevron (▾/▸),无需 PNG 资源;
- 新增 ``preview_textvariable``:折叠时在 header 显示当前配置摘要;
- 新增 ``header_actions``:在 header 右侧注入自定义按钮(如顶部工具条)。
"""
import tkinter
from tkinter import ttk
class CollapsingFrame(ttk.Frame):
"""A modern collapsible frame widget."""
CHEVRON_OPEN = "\u25be" # ▾
CHEVRON_CLOSED = "\u25b8" # ▸
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.columnconfigure(0, weight=1)
self.cumulative_rows = 0
# 兼容旧代码可能引用 self.images
self.images: list = []
# ------------------------------------------------------------------
# 公共 API
# ------------------------------------------------------------------
def add(
self,
child,
title: str = "",
style: str = "primary.TButton", # 兼容旧签名(不再使用)
preview_textvariable=None,
header_actions=None,
**kwargs,
):
"""添加一个子区段到折叠面板。
:param child: 必须是一个 ttk.Frame
:param title: 标题文本;
:param preview_textvariable: 折叠时显示在 header 上的状态摘要 StringVar
:param header_actions: 回调 ``fn(actions_frame)``,可在 header 右侧添加按钮。
"""
if child.winfo_class() != "TFrame":
return
header = ttk.Frame(self, style="ConfigHeader.TFrame", padding=(12, 6))
header.grid(row=self.cumulative_rows, column=0, sticky="ew")
header.columnconfigure(1, weight=1)
# 左chevron + 标题
title_box = ttk.Frame(header, style="ConfigHeader.TFrame")
title_box.grid(row=0, column=0, sticky="w")
chevron = ttk.Label(
title_box, text=self.CHEVRON_OPEN, style="ConfigChevron.TLabel"
)
chevron.pack(side="left", padx=(0, 8))
title_lbl = ttk.Label(title_box, text=title, style="ConfigHeader.TLabel")
if kwargs.get("textvariable"):
title_lbl.configure(textvariable=kwargs.get("textvariable"))
title_lbl.pack(side="left")
# 中:折叠状态预览
preview_lbl = None
if preview_textvariable is not None:
preview_lbl = ttk.Label(
header,
textvariable=preview_textvariable,
style="ConfigPreview.TLabel",
)
preview_lbl.grid(row=0, column=1, sticky="w", padx=(16, 8))
# 右actions如顶部工具条按钮
actions_frame = ttk.Frame(header, style="ConfigHeader.TFrame")
actions_frame.grid(row=0, column=2, sticky="e")
if callable(header_actions):
try:
header_actions(actions_frame)
except Exception:
# 注入失败不应影响整体折叠面板渲染
pass
# 整条 header 点击切换
clickable = [header, title_box, chevron, title_lbl]
if preview_lbl is not None:
clickable.append(preview_lbl)
for w in clickable:
w.bind(
"<Button-1>",
lambda _e, c=child: self._toggle_open_close(c),
)
try:
w.configure(cursor="hand2")
except tkinter.TclError:
pass
child._chevron = chevron
child._header = header
child._preview_lbl = preview_lbl
# 兼容旧代码 child.btn.invoke() / child.btn.configure(image=...)
child.btn = _HeaderToggleProxy(self, child, chevron)
child.grid(row=self.cumulative_rows + 1, column=0, sticky="news")
self.cumulative_rows += 2
# ------------------------------------------------------------------
# 内部实现
# ------------------------------------------------------------------
def _toggle_open_close(self, child):
if child.winfo_viewable():
child.grid_remove()
try:
child._chevron.configure(text=self.CHEVRON_CLOSED)
except (AttributeError, tkinter.TclError):
pass
else:
child.grid()
try:
child._chevron.configure(text=self.CHEVRON_OPEN)
except (AttributeError, tkinter.TclError):
pass
class _HeaderToggleProxy:
"""兼容旧代码:``child.btn.invoke()`` / ``child.btn.configure(image=...)``。"""
def __init__(self, owner: "CollapsingFrame", child, chevron):
self._owner = owner
self._child = child
self._chevron = chevron
def invoke(self):
self._owner._toggle_open_close(self._child)
def configure(self, **kwargs):
image = kwargs.get("image")
if image == "closed":
self._chevron.configure(text=CollapsingFrame.CHEVRON_CLOSED)
elif image == "open":
self._chevron.configure(text=CollapsingFrame.CHEVRON_OPEN)
config = configure
# class Application(tkinter.Tk):
# def __init__(self):
# super().__init__()
# self.title('Collapsing Frame')
# # self.style = Style()
# cf = CollapsingFrame(self)
# cf.pack(fill='both')
# # option group 1
# group1 = ttk.Frame(cf, padding=10)
# for x in range(5):
# ttk.Checkbutton(group1, text=f'Option {x + 1}').pack(fill='x')
# cf.add(group1, title='Option Group 1', style='primary.TButton')
# # option group 2
# group2 = ttk.Frame(cf, padding=10)
# for x in range(5):
# ttk.Checkbutton(group2, text=f'Option {x + 1}').pack(fill='x')
# cf.add(group2, title='Option Group 2', style='danger.TButton')
# if __name__ == '__main__':
# Application().mainloop()