#!/usr/bin/env python # -*- coding: utf-8 -*- """ Production ID 输入控件 多行文本输入框,用于输入 Production ID 列表。 支持内容溢出时自动显示滚动条,左侧显示行号。 """ import tkinter as tk from tkinter import ttk, font from tklinenums import TkLineNumbers 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._updating_placeholder = False # 创建文本框 self.text_widget = tk.Text(self, wrap=tk.WORD, padx=5, pady=5) # 创建行号区域的容器 self.linenums_frame = ttk.Frame(self) # 创建行号控件(使用黑色) self.line_numbers = TkLineNumbers( self.linenums_frame, self.text_widget, justify="center", colors=("black", "#f0f0f0"), bg="#f0f0f0", width=3 ) self.line_numbers.pack(fill="both", expand=True) # 创建滚动条 self.scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text_widget.yview) self.text_widget.configure(yscrollcommand=self._on_scroll) # 布局:行号 | 文本框 | 滚动条 self.linenums_frame.grid(row=0, column=0, sticky="ns") self.text_widget.grid(row=0, column=1, sticky="nsew") self.scrollbar.grid(row=0, column=2, sticky="ns") # 配置行列权重 self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(1, weight=1) # 绑定事件 self.text_widget.bind("", self._on_focus_in) self.text_widget.bind("", self._on_focus_out) # 监听内容变化事件 self.text_widget.bind("", self._on_content_change) self.text_widget.bind("", self._schedule_check) self.text_widget.bind("", self._schedule_check) self.text_widget.bind("", self._schedule_check) # 绑定粘贴事件 self.text_widget.bind("<>", self._schedule_check) # 初始隐藏滚动条(行号始终显示) self.scrollbar.grid_remove() # 显示占位符 self._show_placeholder() # 延迟检查初始状态 self.after(100, self._check_ui_state) def _on_focus_in(self, event): """获得焦点时隐藏占位符""" if not self._updating_placeholder and 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 _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._updating_placeholder = True self.text_widget.delete("1.0", tk.END) self.text_widget.insert("1.0", self.placeholder) self.text_widget.configure(foreground="gray") self._updating_placeholder = False self._schedule_check() def _hide_placeholder(self): """隐藏占位符""" self._updating_placeholder = True 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") self._updating_placeholder = False def _on_content_change(self, event=None): """内容变化时的处理""" # 如果不是占位符状态,重绘行号 if self.text_widget.get("1.0", "end-1c") != self.placeholder: self.line_numbers.redraw() self._schedule_check() 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._updating_placeholder = True 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") self.after_idle(self.line_numbers.redraw) else: self._show_placeholder() self._updating_placeholder = False self._schedule_check() def clear(self): """清空内容""" self._updating_placeholder = True self.text_widget.delete("1.0", tk.END) self._show_placeholder() self._updating_placeholder = False 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.after_idle(self.line_numbers.redraw) 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) # 重绘行号以应用字体变化 self.after_idle(self.line_numbers.redraw) def _on_scroll(self, first, last): """滚动回调,更新滚动条位置和行号""" self.scrollbar.set(first, last) # 滚动时重绘行号以同步显示 self.line_numbers.redraw() def _schedule_check(self, event=None): """调度 UI 状态检查(防抖)""" if not self._check_pending: self._check_pending = True self.after(50, self._check_ui_state) def _check_ui_state(self): """检查 UI 状态(滚动条)""" self._check_pending = False # 更新界面以确保获取准确的尺寸 self.text_widget.update_idletasks() # 检查是否需要显示滚动条 first, last = self.text_widget.yview() 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