Files
playwrite/gui/widgets/production_id_input.py
Misaka ee0b4187d7 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>
2026-02-11 21:59:56 +08:00

148 lines
5.2 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Production ID 输入控件
多行文本输入框,用于输入 Production ID 列表。
支持内容溢出时自动显示滚动条。
"""
import tkinter as tk
from tkinter import ttk, font
class ProductionIdInput(ttk.Frame):
"""Production ID 输入控件"""
def __init__(self, parent, placeholder="每行输入一个 Production ID", **kwargs):
super().__init__(parent, **kwargs)
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.scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text_widget.yview)
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("<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.after(100, self._check_scrollbar_needed)
def _on_focus_in(self, event):
"""获得焦点时隐藏占位符"""
if self.text_widget.get("1.0", "end-1c") == self.placeholder:
self.text_widget.delete("1.0", tk.END)
def _on_focus_out(self, event):
"""失去焦点时显示占位符"""
content = self.text_widget.get("1.0", "end-1c")
if not content:
self._show_placeholder()
def _show_placeholder(self):
"""显示占位符"""
self.text_widget.delete("1.0", tk.END)
self.text_widget.insert("1.0", self.placeholder)
self.text_widget.configure(foreground="gray")
def _hide_placeholder(self):
"""隐藏占位符"""
if self.text_widget.get("1.0", "end-1c") == self.placeholder:
self.text_widget.delete("1.0", tk.END)
self.text_widget.configure(foreground="black")
def get(self) -> list[str]:
"""获取 Production ID 列表"""
self._hide_placeholder()
content = self.text_widget.get("1.0", "end-1c").strip()
return [line.strip() for line in content.split("\n") if line.strip()]
def set(self, production_ids: list[str]):
"""设置 Production ID 列表"""
self.text_widget.delete("1.0", tk.END)
if production_ids:
self.text_widget.insert("1.0", "\n".join(production_ids))
self.text_widget.configure(foreground="black")
else:
self._show_placeholder()
self._schedule_check()
def clear(self):
"""清空内容"""
self.text_widget.delete("1.0", tk.END)
self._show_placeholder()
self._schedule_check()
def append(self, production_ids: list[str]):
"""追加 Production ID 列表"""
self._hide_placeholder()
if production_ids:
current_content = self.text_widget.get("1.0", "end-1c")
if current_content.strip():
self.text_widget.insert(tk.END, "\n" + "\n".join(production_ids))
else:
self.text_widget.insert("1.0", "\n".join(production_ids))
self._schedule_check()
def apply_font(self, font_family: str, font_size: int):
"""应用字体设置"""
font_spec = font.Font(family=font_family, size=font_size)
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