tkinter 中变量 window 大小的高效 text-wrapping
Efficient text-wrapping for variable window sizes in tkinter
我正在制作一个输出大量可滚动文本的程序。我希望文本根据 window 大小自动换行,并且能够通过配置和自动调整它的事件函数来实现。本来,用户需要点击一个按钮来切换页面来显示结果,是这样的:
import tkinter as tk
class textwrap(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
self.grid()
self.page = 0
self.results = []
self.go = tk.Button(self, text = "Go", command = self.run)
self.go.grid()
self.swap_button = tk.Button(self, text = "Change page", command = self.swap)
self.swap_button.grid()
def run(self):
for x in range(1000):
self.results.append('abcdefg abcdefg')
def swap(self):
if self.page == 0:
self.go.destroy()
self.page = 1
self.results_label = tk.Label(self, text = self.results, wraplength = self.master.winfo_width())
self.results_label.grid()
else:
self.results_label.destroy()
self.page = 0
self.go = tk.Button(self, text = "Go", command = self.run)
self.go.grid(row = 0)
frame01 = textwrap()
frame01.mainloop()
我在这里排除了 auto-wrapping 和滚动条。这个模型的问题很快就会变得明显,就是输入过多的文本最终会导致程序在尝试显示数据时死机。我尝试使用 ScrolledText 框(内置滚动条的文本字段)找到解决方法,将其存储在 ttk 笔记本的第二个选项卡中,并像这样实时写入它:
import tkinter as tk
from tkinter import scrolledtext
class textwrap2(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
self.grid()
self.text_field = scrolledtext.ScrolledText(state = 'disabled')
self.text_field.grid()
self.go = tk.Button(self, text = "Go", command = self.run)
self.go.grid()
def run(self):
self.text_field.config(state='normal')
for x in range(1000):
self.text_field.insert(tk.END, x)
self.text_field.config(state='disabled')
frame01 = textwrap2()
frame01.mainloop()
虽然这没有打开时的初始延迟,但在添加大量数据后整个程序开始变慢,并且滚动条非常延迟,甚至比之前成功加载等量数据时还要糟糕版本。
我要问的实际上是:是否有任何有效的方法来创建带有滚动条的大尺寸包装文本? 试图加载一个非常大的列表一旦冻结程序,并在创建时实时加载它会导致程序作为一个整体变慢,因为它变得更大。我考虑过用迭代创建的无数标签实时加载它,但不确定如何将它们包装为一个集体。另一个关于滚动条滞后的问题建议在一定距离后自动插入换行符,而不是使用自动换行。问题是,如果 window 被调整过大小,换行将不再准确,并且在那之后没有简单的方法来修复它。 Windows 上的简单记事本应用程序让 child 解决了这个问题,所以我想知道是否有一个明显的解决方案在逃避我。
当文本行很长时,文本小部件的性能不佳。来自 tk 工具包生命早期写的一篇论文(写于 1996 年左右):
Alas, very long lines are also slow in Tk text widgets, (...) . Tk uses a b-tree to represent the contents of the widget, but the leaves of the b-tree are lines (everything between two newline characters), so Tk has to do linear searches through lines to find anything.
解决方法成功(虽然它在我原来的程序中引入了新问题,但这超出了这个问题的范围)。使用标签滚动比使用文本字段滚动要流畅得多,所以我又回到了那个。一次加载过大的标签会导致问题,所以我通过自动将输入分成一定大小的单独标签来避免这种情况。虽然这确实会在这些地方造成不完美的换行,但标签尺寸足够大,总体上不会造成太大问题。代码将是这样的:
import tkinter as tk
class textwrap3(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
self.pack()
self.master.geometry("1280x720")
self.master.update_idletasks()
self.inputs_list = []
self.starting_thousand = 0
self.ending_thousand = 7000
#Creates the canvas with frame to hold data, and vertical scrollbar.
#This part isn't the solution, but is required to visualize how the solution works.
self.results_canvas = tk.Canvas(self, width = self.master.winfo_width()-20, height = self.master.winfo_height()-30)
self.results_canvas.grid(sticky='new')
self.scroll = tk.Scrollbar(self, orient = 'vertical', command = self.results_canvas.yview)
self.results_canvas.configure(yscrollcommand=self.scroll.set)
self.results_frame = tk.Frame(self.results_canvas)
self.results_canvas.create_window((0,0), window = self.results_frame, anchor = 'n')
self.results_frame.bind("<Configure>", lambda e: self.results_canvas.configure
(scrollregion = self.results_canvas.bbox("all")))
self.scroll.grid(row = 0, column = 1, sticky = 'nse')
self.current_thousand_label = tk.Label(self.results_frame, wraplength = self.master.winfo_width()-20)
self.current_thousand_label.grid()
self.go = tk.Button(self, text = "Go", command = self.run)
self.go.grid()
self.bind('<Configure>', self.wrap)
#Automaticlly wraps the text
def wrap(self, event):
self.results_canvas.config(width = self.master.winfo_width()-20, height = self.master.winfo_height() - 30)
self.results_frame.config(width = self.master.winfo_width()-20)
for widget in self.results_frame.winfo_children():
widget.config(wraplength=self.master.winfo_width()-20)
def run(self):
for x in range(99700,100300):
self.inputs_list.append(x)
self.thousand_wrap()
self.current_thousand_label.config(text = self.inputs_list[self.starting_thousand:])
#The workaround itself
def thousand_wrap(self):
if len(self.inputs_list) >= self.ending_thousand:
self.current_thousand_label.destroy()
self.previous_thousand_labels = tk.Label(self.results_frame,
text = self.inputs_list[self.starting_thousand:self.ending_thousand],
wraplength = self.master.winfo_width()-20)
self.previous_thousand_labels.grid()
self.starting_thousand += 7000
self.ending_thousand += 7000
self.current_thousand_label = tk.Label(self.results_frame, wraplength = self.master.winfo_width()-20)
self.current_thousand_label.grid()
frame01 = textwrap3()
frame01.mainloop()
达到一定数量的大标签后,文本将被截断。我认为这与 tkinter 画布的固有大小限制有关,但这超出了我最初问题的范围,我相信这 proof-of-concept 显示了至少可以显示多少数据而不会出现大量程序延迟。您可以更改 self.ending_thousand
以及 thousand_wrap()
下开始和结束千位的添加,以更改每个标签的大小。
我正在制作一个输出大量可滚动文本的程序。我希望文本根据 window 大小自动换行,并且能够通过配置和自动调整它的事件函数来实现。本来,用户需要点击一个按钮来切换页面来显示结果,是这样的:
import tkinter as tk
class textwrap(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
self.grid()
self.page = 0
self.results = []
self.go = tk.Button(self, text = "Go", command = self.run)
self.go.grid()
self.swap_button = tk.Button(self, text = "Change page", command = self.swap)
self.swap_button.grid()
def run(self):
for x in range(1000):
self.results.append('abcdefg abcdefg')
def swap(self):
if self.page == 0:
self.go.destroy()
self.page = 1
self.results_label = tk.Label(self, text = self.results, wraplength = self.master.winfo_width())
self.results_label.grid()
else:
self.results_label.destroy()
self.page = 0
self.go = tk.Button(self, text = "Go", command = self.run)
self.go.grid(row = 0)
frame01 = textwrap()
frame01.mainloop()
我在这里排除了 auto-wrapping 和滚动条。这个模型的问题很快就会变得明显,就是输入过多的文本最终会导致程序在尝试显示数据时死机。我尝试使用 ScrolledText 框(内置滚动条的文本字段)找到解决方法,将其存储在 ttk 笔记本的第二个选项卡中,并像这样实时写入它:
import tkinter as tk
from tkinter import scrolledtext
class textwrap2(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
self.grid()
self.text_field = scrolledtext.ScrolledText(state = 'disabled')
self.text_field.grid()
self.go = tk.Button(self, text = "Go", command = self.run)
self.go.grid()
def run(self):
self.text_field.config(state='normal')
for x in range(1000):
self.text_field.insert(tk.END, x)
self.text_field.config(state='disabled')
frame01 = textwrap2()
frame01.mainloop()
虽然这没有打开时的初始延迟,但在添加大量数据后整个程序开始变慢,并且滚动条非常延迟,甚至比之前成功加载等量数据时还要糟糕版本。
我要问的实际上是:是否有任何有效的方法来创建带有滚动条的大尺寸包装文本? 试图加载一个非常大的列表一旦冻结程序,并在创建时实时加载它会导致程序作为一个整体变慢,因为它变得更大。我考虑过用迭代创建的无数标签实时加载它,但不确定如何将它们包装为一个集体。另一个关于滚动条滞后的问题建议在一定距离后自动插入换行符,而不是使用自动换行。问题是,如果 window 被调整过大小,换行将不再准确,并且在那之后没有简单的方法来修复它。 Windows 上的简单记事本应用程序让 child 解决了这个问题,所以我想知道是否有一个明显的解决方案在逃避我。
当文本行很长时,文本小部件的性能不佳。来自 tk 工具包生命早期写的一篇论文(写于 1996 年左右):
Alas, very long lines are also slow in Tk text widgets, (...) . Tk uses a b-tree to represent the contents of the widget, but the leaves of the b-tree are lines (everything between two newline characters), so Tk has to do linear searches through lines to find anything.
解决方法成功(虽然它在我原来的程序中引入了新问题,但这超出了这个问题的范围)。使用标签滚动比使用文本字段滚动要流畅得多,所以我又回到了那个。一次加载过大的标签会导致问题,所以我通过自动将输入分成一定大小的单独标签来避免这种情况。虽然这确实会在这些地方造成不完美的换行,但标签尺寸足够大,总体上不会造成太大问题。代码将是这样的:
import tkinter as tk
class textwrap3(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
self.pack()
self.master.geometry("1280x720")
self.master.update_idletasks()
self.inputs_list = []
self.starting_thousand = 0
self.ending_thousand = 7000
#Creates the canvas with frame to hold data, and vertical scrollbar.
#This part isn't the solution, but is required to visualize how the solution works.
self.results_canvas = tk.Canvas(self, width = self.master.winfo_width()-20, height = self.master.winfo_height()-30)
self.results_canvas.grid(sticky='new')
self.scroll = tk.Scrollbar(self, orient = 'vertical', command = self.results_canvas.yview)
self.results_canvas.configure(yscrollcommand=self.scroll.set)
self.results_frame = tk.Frame(self.results_canvas)
self.results_canvas.create_window((0,0), window = self.results_frame, anchor = 'n')
self.results_frame.bind("<Configure>", lambda e: self.results_canvas.configure
(scrollregion = self.results_canvas.bbox("all")))
self.scroll.grid(row = 0, column = 1, sticky = 'nse')
self.current_thousand_label = tk.Label(self.results_frame, wraplength = self.master.winfo_width()-20)
self.current_thousand_label.grid()
self.go = tk.Button(self, text = "Go", command = self.run)
self.go.grid()
self.bind('<Configure>', self.wrap)
#Automaticlly wraps the text
def wrap(self, event):
self.results_canvas.config(width = self.master.winfo_width()-20, height = self.master.winfo_height() - 30)
self.results_frame.config(width = self.master.winfo_width()-20)
for widget in self.results_frame.winfo_children():
widget.config(wraplength=self.master.winfo_width()-20)
def run(self):
for x in range(99700,100300):
self.inputs_list.append(x)
self.thousand_wrap()
self.current_thousand_label.config(text = self.inputs_list[self.starting_thousand:])
#The workaround itself
def thousand_wrap(self):
if len(self.inputs_list) >= self.ending_thousand:
self.current_thousand_label.destroy()
self.previous_thousand_labels = tk.Label(self.results_frame,
text = self.inputs_list[self.starting_thousand:self.ending_thousand],
wraplength = self.master.winfo_width()-20)
self.previous_thousand_labels.grid()
self.starting_thousand += 7000
self.ending_thousand += 7000
self.current_thousand_label = tk.Label(self.results_frame, wraplength = self.master.winfo_width()-20)
self.current_thousand_label.grid()
frame01 = textwrap3()
frame01.mainloop()
达到一定数量的大标签后,文本将被截断。我认为这与 tkinter 画布的固有大小限制有关,但这超出了我最初问题的范围,我相信这 proof-of-concept 显示了至少可以显示多少数据而不会出现大量程序延迟。您可以更改 self.ending_thousand
以及 thousand_wrap()
下开始和结束千位的添加,以更改每个标签的大小。