feat: add auto-show scrollbar for Production ID input widget

When content overflows in the Production ID text input, the scrollbar
now automatically appears. When content fits within the visible area,
the scrollbar is hidden.

- Changed layout from pack to grid for dynamic scrollbar control
- Added yview-based overflow detection (last < 1.0 indicates overflow)
- Added debounce mechanism to prevent excessive checks
- Bound events: KeyRelease, ButtonRelease, Configure, Paste

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-02-11 21:59:56 +08:00
parent 6f8df2f2e6
commit ee0b4187d7

View File

@@ -4,6 +4,7 @@
Production ID 输入控件 Production ID 输入控件
多行文本输入框,用于输入 Production ID 列表。 多行文本输入框,用于输入 Production ID 列表。
支持内容溢出时自动显示滚动条。
""" """
import tkinter as tk import tkinter as tk
@@ -16,23 +17,44 @@ class ProductionIdInput(ttk.Frame):
def __init__(self, parent, placeholder="每行输入一个 Production ID", **kwargs): def __init__(self, parent, placeholder="每行输入一个 Production ID", **kwargs):
super().__init__(parent, **kwargs) super().__init__(parent, **kwargs)
self.placeholder = placeholder self.placeholder = placeholder
self._scrollbar_visible = False
self._check_pending = False
# 创建文本框和滚动条 # 创建文本框
self.text_widget = tk.Text(self, wrap=tk.WORD, padx=5, pady=5) self.text_widget = tk.Text(self, wrap=tk.WORD, padx=5, pady=5)
self.scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text_widget.yview)
self.text_widget.configure(yscrollcommand=self.scrollbar.set)
# 布局 # 创建滚动条
self.text_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text_widget.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.text_widget.configure(yscrollcommand=self._on_scroll)
# 布局 - 使用 grid 以便动态显示/隐藏滚动条
self.text_widget.grid(row=0, column=0, sticky="nsew")
self.scrollbar.grid(row=0, column=1, sticky="ns")
# 配置行列权重
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
# 绑定事件 # 绑定事件
self.text_widget.bind("<FocusIn>", self._on_focus_in) self.text_widget.bind("<FocusIn>", self._on_focus_in)
self.text_widget.bind("<FocusOut>", self._on_focus_out) self.text_widget.bind("<FocusOut>", self._on_focus_out)
# 监听内容变化事件
self.text_widget.bind("<KeyRelease>", self._schedule_check)
self.text_widget.bind("<ButtonRelease-1>", self._schedule_check)
self.text_widget.bind("<ButtonRelease-3>", self._schedule_check)
self.text_widget.bind("<Configure>", self._schedule_check)
# 绑定粘贴事件
self.text_widget.bind("<<Paste>>", self._schedule_check)
# 初始隐藏滚动条
self.scrollbar.grid_remove()
# 显示占位符 # 显示占位符
self._show_placeholder() self._show_placeholder()
# 延迟检查初始状态
self.after(100, self._check_scrollbar_needed)
def _on_focus_in(self, event): def _on_focus_in(self, event):
"""获得焦点时隐藏占位符""" """获得焦点时隐藏占位符"""
if self.text_widget.get("1.0", "end-1c") == self.placeholder: if self.text_widget.get("1.0", "end-1c") == self.placeholder:
@@ -70,11 +92,13 @@ class ProductionIdInput(ttk.Frame):
self.text_widget.configure(foreground="black") self.text_widget.configure(foreground="black")
else: else:
self._show_placeholder() self._show_placeholder()
self._schedule_check()
def clear(self): def clear(self):
"""清空内容""" """清空内容"""
self.text_widget.delete("1.0", tk.END) self.text_widget.delete("1.0", tk.END)
self._show_placeholder() self._show_placeholder()
self._schedule_check()
def append(self, production_ids: list[str]): def append(self, production_ids: list[str]):
"""追加 Production ID 列表""" """追加 Production ID 列表"""
@@ -85,8 +109,39 @@ class ProductionIdInput(ttk.Frame):
self.text_widget.insert(tk.END, "\n" + "\n".join(production_ids)) self.text_widget.insert(tk.END, "\n" + "\n".join(production_ids))
else: else:
self.text_widget.insert("1.0", "\n".join(production_ids)) self.text_widget.insert("1.0", "\n".join(production_ids))
self._schedule_check()
def apply_font(self, font_family: str, font_size: int): def apply_font(self, font_family: str, font_size: int):
"""应用字体设置""" """应用字体设置"""
font_spec = font.Font(family=font_family, size=font_size) font_spec = font.Font(family=font_family, size=font_size)
self.text_widget.configure(font=font_spec) self.text_widget.configure(font=font_spec)
def _on_scroll(self, first, last):
"""滚动回调,更新滚动条位置"""
self.scrollbar.set(first, last)
def _schedule_check(self, event=None):
"""调度滚动条检查(防抖)"""
if not self._check_pending:
self._check_pending = True
self.after(50, self._check_scrollbar_needed)
def _check_scrollbar_needed(self):
"""检查是否需要显示滚动条"""
self._check_pending = False
# 更新界面以确保获取准确的尺寸
self.text_widget.update_idletasks()
# 使用 yview() 返回值判断
first, last = self.text_widget.yview()
# last < 1.0 表示内容超出了可见区域(需要滚动条)
needs_scrollbar = last < 1.0
if needs_scrollbar != self._scrollbar_visible:
if needs_scrollbar:
self.scrollbar.grid()
self._scrollbar_visible = True
else:
self.scrollbar.grid_remove()
self._scrollbar_visible = False