feat: add line numbers to Production ID input widget
- Integrate tklinenums library for line number display - Line numbers appear on the left side in black color - Line numbers are always visible (even with placeholder) - Line numbers sync with text scrolling - Add tklinenums>=1.7.0 to requirements.txt Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,61 +4,80 @@
|
|||||||
Production ID 输入控件
|
Production ID 输入控件
|
||||||
|
|
||||||
多行文本输入框,用于输入 Production ID 列表。
|
多行文本输入框,用于输入 Production ID 列表。
|
||||||
支持内容溢出时自动显示滚动条。
|
支持内容溢出时自动显示滚动条,左侧显示行号。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk, font
|
from tkinter import ttk, font
|
||||||
|
from tklinenums import TkLineNumbers
|
||||||
|
|
||||||
|
|
||||||
class ProductionIdInput(ttk.Frame):
|
class ProductionIdInput(ttk.Frame):
|
||||||
"""Production ID 输入控件"""
|
"""Production ID 输入控件,带行号和自动滚动条"""
|
||||||
|
|
||||||
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._scrollbar_visible = False
|
||||||
self._check_pending = False
|
self._check_pending = False
|
||||||
|
self._updating_placeholder = 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.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.scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text_widget.yview)
|
||||||
self.text_widget.configure(yscrollcommand=self._on_scroll)
|
self.text_widget.configure(yscrollcommand=self._on_scroll)
|
||||||
|
|
||||||
# 布局 - 使用 grid 以便动态显示/隐藏滚动条
|
# 布局:行号 | 文本框 | 滚动条
|
||||||
self.text_widget.grid(row=0, column=0, sticky="nsew")
|
self.linenums_frame.grid(row=0, column=0, sticky="ns")
|
||||||
self.scrollbar.grid(row=0, column=1, 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_rowconfigure(0, weight=1)
|
||||||
self.grid_columnconfigure(0, weight=1)
|
self.grid_columnconfigure(1, 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("<KeyRelease>", self._on_content_change)
|
||||||
self.text_widget.bind("<ButtonRelease-1>", 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("<ButtonRelease-3>", self._schedule_check)
|
||||||
self.text_widget.bind("<Configure>", self._schedule_check)
|
self.text_widget.bind("<Configure>", self._schedule_check)
|
||||||
# 绑定粘贴事件
|
# 绑定粘贴事件
|
||||||
self.text_widget.bind("<<Paste>>", self._schedule_check)
|
self.text_widget.bind("<<Paste>>", self._schedule_check)
|
||||||
|
|
||||||
# 初始隐藏滚动条
|
# 初始隐藏滚动条(行号始终显示)
|
||||||
self.scrollbar.grid_remove()
|
self.scrollbar.grid_remove()
|
||||||
|
|
||||||
# 显示占位符
|
# 显示占位符
|
||||||
self._show_placeholder()
|
self._show_placeholder()
|
||||||
|
|
||||||
# 延迟检查初始状态
|
# 延迟检查初始状态
|
||||||
self.after(100, self._check_scrollbar_needed)
|
self.after(100, self._check_ui_state)
|
||||||
|
|
||||||
def _on_focus_in(self, event):
|
def _on_focus_in(self, event):
|
||||||
"""获得焦点时隐藏占位符"""
|
"""获得焦点时隐藏占位符"""
|
||||||
if self.text_widget.get("1.0", "end-1c") == self.placeholder:
|
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.delete("1.0", tk.END)
|
||||||
|
# 确保文字颜色为黑色
|
||||||
|
self.text_widget.configure(foreground="black")
|
||||||
|
|
||||||
def _on_focus_out(self, event):
|
def _on_focus_out(self, event):
|
||||||
"""失去焦点时显示占位符"""
|
"""失去焦点时显示占位符"""
|
||||||
@@ -68,15 +87,27 @@ class ProductionIdInput(ttk.Frame):
|
|||||||
|
|
||||||
def _show_placeholder(self):
|
def _show_placeholder(self):
|
||||||
"""显示占位符"""
|
"""显示占位符"""
|
||||||
|
self._updating_placeholder = True
|
||||||
self.text_widget.delete("1.0", tk.END)
|
self.text_widget.delete("1.0", tk.END)
|
||||||
self.text_widget.insert("1.0", self.placeholder)
|
self.text_widget.insert("1.0", self.placeholder)
|
||||||
self.text_widget.configure(foreground="gray")
|
self.text_widget.configure(foreground="gray")
|
||||||
|
self._updating_placeholder = False
|
||||||
|
self._schedule_check()
|
||||||
|
|
||||||
def _hide_placeholder(self):
|
def _hide_placeholder(self):
|
||||||
"""隐藏占位符"""
|
"""隐藏占位符"""
|
||||||
|
self._updating_placeholder = True
|
||||||
if self.text_widget.get("1.0", "end-1c") == self.placeholder:
|
if self.text_widget.get("1.0", "end-1c") == self.placeholder:
|
||||||
self.text_widget.delete("1.0", tk.END)
|
self.text_widget.delete("1.0", tk.END)
|
||||||
self.text_widget.configure(foreground="black")
|
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]:
|
def get(self) -> list[str]:
|
||||||
"""获取 Production ID 列表"""
|
"""获取 Production ID 列表"""
|
||||||
@@ -86,18 +117,23 @@ class ProductionIdInput(ttk.Frame):
|
|||||||
|
|
||||||
def set(self, production_ids: list[str]):
|
def set(self, production_ids: list[str]):
|
||||||
"""设置 Production ID 列表"""
|
"""设置 Production ID 列表"""
|
||||||
|
self._updating_placeholder = True
|
||||||
self.text_widget.delete("1.0", tk.END)
|
self.text_widget.delete("1.0", tk.END)
|
||||||
if production_ids:
|
if production_ids:
|
||||||
self.text_widget.insert("1.0", "\n".join(production_ids))
|
self.text_widget.insert("1.0", "\n".join(production_ids))
|
||||||
self.text_widget.configure(foreground="black")
|
self.text_widget.configure(foreground="black")
|
||||||
|
self.after_idle(self.line_numbers.redraw)
|
||||||
else:
|
else:
|
||||||
self._show_placeholder()
|
self._show_placeholder()
|
||||||
|
self._updating_placeholder = False
|
||||||
self._schedule_check()
|
self._schedule_check()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""清空内容"""
|
"""清空内容"""
|
||||||
|
self._updating_placeholder = True
|
||||||
self.text_widget.delete("1.0", tk.END)
|
self.text_widget.delete("1.0", tk.END)
|
||||||
self._show_placeholder()
|
self._show_placeholder()
|
||||||
|
self._updating_placeholder = False
|
||||||
self._schedule_check()
|
self._schedule_check()
|
||||||
|
|
||||||
def append(self, production_ids: list[str]):
|
def append(self, production_ids: list[str]):
|
||||||
@@ -109,33 +145,37 @@ 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.after_idle(self.line_numbers.redraw)
|
||||||
self._schedule_check()
|
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)
|
||||||
|
# 重绘行号以应用字体变化
|
||||||
|
self.after_idle(self.line_numbers.redraw)
|
||||||
|
|
||||||
def _on_scroll(self, first, last):
|
def _on_scroll(self, first, last):
|
||||||
"""滚动回调,更新滚动条位置"""
|
"""滚动回调,更新滚动条位置和行号"""
|
||||||
self.scrollbar.set(first, last)
|
self.scrollbar.set(first, last)
|
||||||
|
# 滚动时重绘行号以同步显示
|
||||||
|
self.line_numbers.redraw()
|
||||||
|
|
||||||
def _schedule_check(self, event=None):
|
def _schedule_check(self, event=None):
|
||||||
"""调度滚动条检查(防抖)"""
|
"""调度 UI 状态检查(防抖)"""
|
||||||
if not self._check_pending:
|
if not self._check_pending:
|
||||||
self._check_pending = True
|
self._check_pending = True
|
||||||
self.after(50, self._check_scrollbar_needed)
|
self.after(50, self._check_ui_state)
|
||||||
|
|
||||||
def _check_scrollbar_needed(self):
|
def _check_ui_state(self):
|
||||||
"""检查是否需要显示滚动条"""
|
"""检查 UI 状态(滚动条)"""
|
||||||
self._check_pending = False
|
self._check_pending = False
|
||||||
|
|
||||||
# 更新界面以确保获取准确的尺寸
|
# 更新界面以确保获取准确的尺寸
|
||||||
self.text_widget.update_idletasks()
|
self.text_widget.update_idletasks()
|
||||||
|
|
||||||
# 使用 yview() 返回值判断
|
# 检查是否需要显示滚动条
|
||||||
first, last = self.text_widget.yview()
|
first, last = self.text_widget.yview()
|
||||||
# last < 1.0 表示内容超出了可见区域(需要滚动条)
|
|
||||||
needs_scrollbar = last < 1.0
|
needs_scrollbar = last < 1.0
|
||||||
|
|
||||||
if needs_scrollbar != self._scrollbar_visible:
|
if needs_scrollbar != self._scrollbar_visible:
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ numpy>=1.24.0
|
|||||||
python-dateutil>=2.8.0
|
python-dateutil>=2.8.0
|
||||||
pytz>=2023.0
|
pytz>=2023.0
|
||||||
python-dotenv>=1.0.0
|
python-dotenv>=1.0.0
|
||||||
|
tklinenums>=1.7.0
|
||||||
|
|||||||
Reference in New Issue
Block a user