请告诉我有更好的方法来设置 ScrolledText 视图 window
Please tell me there's a better way to set the ScrolledText view window
我有这个示例代码来探索 ScrolledText 的内部工作原理,试图找出原因:
- 替换文本内容后设置视图window滚动条闪烁
- 如果文本稍微超出视图 window,则视图 window 设置为我指定的值以外的值。请注意,这是 而不是 由行舍入引起的,因为该值被多行关闭。
- 如果文本大大超过视图 window,而视图 window 最初设置为我指定的值,然后它会被覆盖两次。最后一次导致视图 window 跳转到一个非常不正确的位置。大概导致 [1].
#!/usr/bin/env python3
import tkinter as tk
# This test program is a stand-in for ScrolledText, so I can examine
# the effects of 'yscrollcommand'.
#from tkinter import scrolledtext
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
text_out = tk.Text(frame, state=tk.DISABLED)
text_out.pack(side=tk.LEFT)
def from_scollbar_yview(*args, **kwargs):
print("\ntext_out.yview: ", args, kwargs)
return text_out.yview(*args, **kwargs)
scrollbar = tk.Scrollbar(frame, command=from_scollbar_yview)
scrollbar.pack(side=tk.LEFT, fill='y')
button = tk.Button(root, text="copy")
button.pack()
text_in = tk.Text(root)
text_in.pack()
def from_text_set(*args, **kwargs):
print("\nscrollbar.set: ", args, kwargs)
print("... and yview is: ", text_out.yview())
return scrollbar.set(*args, **kwargs)
text_out.configure(yscrollcommand = from_text_set)
def on_click(event):
y1 = text_out.yview()
print("\ncopy start: ", y1)
s = text_in.get("1.0","end-1c")
# Attempted work-around. If the explanations below had been
# correct this, though extremely hacky, would have worked.
#yc = text_out.cget("yscrollcommand")
#i = 0
#def yc_ignore_n(*args):
# nonlocal i
# print("called yscrollcommand: ", i, *args)
# if i >= 1:
# print("setting yview[0]: ", y2[0])
# text_out.yview("moveto", y2[0])
# text_out.configure(yscrollcommand = yc)
# print("yscrollcommand restored!!")
# i += 1
# return True
#if y1[0] > 0 or y1[1] < 1:
# print("implementing workaround")
# text_out.configure(yscrollcommand = yc_ignore_n)
print(f"copying text: {len(s)} chars")
text_out.configure(state=tk.NORMAL)
# I now think these explanations are incorrect:
#
# If the text height changes, 'delete' will queue a
# 'yscrollcommand' at the *front*. This *will* mess
# with the 'yview'.
text_out.delete(1.0, "end")
# If the text height changes, 'insert' will queue a
# 'yscrollcommand' at the *front* (before 'delete').
text_out.insert("end-1c", s)
text_out.configure(state=tk.DISABLED)
print("setting yview[0]: ", 0.25)
text_out.yview("moveto", 0.25)
# When the contents only slightly exceed the size of the
# text, 'yview' is set to an incorrect value. Why?
y2 = text_out.yview()
print("copy stop:", y2)
button.bind("<Button-1>", on_click)
root.mainloop()
注意解决方法已被注释掉。它只适用于 [3],所以继续粘贴一个很长的字符串。我正在使用 this Lorem Ipsum generator.
这是一个很长的字符串 [3] 的输出。我试图将视图 window 的顶部设置为 25%,但最终设置为 15%。我的代码中的注释解释了为什么会这样。
...
copy start: (0.0, 1.0)
copying text: 2623 chars
setting yview[0]: 0.25
copy stop: (0.25, 1.0)
scrollbar.set: ('0.25', '1.0') {}
... and yview is: (0.25, 1.0)
scrollbar.set: ('0.15625', '0.75625') {}
... and yview is: (0.15625, 0.75625)
这是一个稍长的字符串 [2].
的输出
copy start: (0.1724137931034483, 1.0)
copying text: 2569 chars
setting yview[0]: 0.25
copy stop: (0.35135135135135137, 1.0)
scrollbar.set: ('0.35135135135135137', '1.0') {}
... and yview is: (0.35135135135135137, 1.0)
虽然我现在认为代码注释中的解释不正确,但我可以肯定它们是由yscrollcommand
被过度调用引起的。我无法在不破坏滚动条的情况下完全禁用 yscrollcommand
。我无法确定在所有排队 yscollcommand
之后设置正确 yview 的回调将 运行,也不会修复闪烁 [1].
第一个 yscrolledcommand
正在排队等待状态更改 (normal/disabled)。来自 insert
(不是 delete
)的第二个在文本视图 外部 换行时触发。可能将视图位置调整为任何换行。由于调整会导致滚动条缩小,这意味着它最初将换行视为单行,然后将换行视为多行。正常25%就是25%,但是因为调整的时候view不能移动,现在内容多了,top position的百分比就要降低。这就是导致闪烁的原因。
对于部分模糊的回绕线,这仅在底部触发。
window视图的初始配置只考虑可见的回绕线,不会引起任何闪烁。对于部分模糊的环绕线,这只考虑顶部的线。在处理回绕线时,初始和最终视图配置完全相反。
无论如何,我想证明 Tk 的做法是完全错误的。假设我有 20 条非换行,一条单行换行成 20 行,还有 20 条非换行。我想将滚动的顶部 window 设置为 25%,这意味着换行将完全可见。
在 25% 时,这是应该可见的第一行:
(12+20*2)*.25
>>> 13.0
但是 Tk 显示了第 11 行的一部分。这对应什么?
(1+20*2)*.25
>>> 10.25
10.25/(12+20*2)
>>> 0.1971153846153846
这就是 Tk 根据我的 yview
调整到:粗略的 19%。
现在假设我有 40 条非换行和一条换行成 20 行的单行。我想将卷轴的顶部 window 设置为 25%,这意味着不会显示任何换行。
我会展示计算结果,但它们几乎完全相同。唯一的区别是滚动条会闪烁。相反:
copy start: (0.0, 0.46153846153846156)
copying text: 1025 chars
setting yview[0]: 0.25
copy stop: (0.25, 0.8353658536585366)
scrollbar.set: ('0.25', '0.8353658536585366') {}
... and yview is: (0.25, 0.8353658536585366)
scrollbar.set: ('0.1971153846153846', '0.6586538461538461') {}
... and yview is: (0.1971153846153846, 0.6586538461538461)
那么我们如何解决这个问题呢?您可以尝试撤消 Tk 的数学运算,向后工作以获得将转换为您想要的值的值。但是,您仍然会闪烁。最好的解决方案是自己换行 (wrap = "none"
)。为此,您需要 window 尺寸。虽然 window 大小是根据创建时的字体大小指定的,但内部并不维护。您只能使用 winfo_width
获得 window 大小(以像素为单位)。幸运的是 Font.measure
为实例化字体提供给定字符串的像素宽度。
唯一的问题是值得吗?每次调整小部件大小时,您都必须绑定到 <Configure>
才能重新包装。这也意味着决定如何调整滚动条和显示哪个视图。
我有这个示例代码来探索 ScrolledText 的内部工作原理,试图找出原因:
- 替换文本内容后设置视图window滚动条闪烁
- 如果文本稍微超出视图 window,则视图 window 设置为我指定的值以外的值。请注意,这是 而不是 由行舍入引起的,因为该值被多行关闭。
- 如果文本大大超过视图 window,而视图 window 最初设置为我指定的值,然后它会被覆盖两次。最后一次导致视图 window 跳转到一个非常不正确的位置。大概导致 [1].
#!/usr/bin/env python3
import tkinter as tk
# This test program is a stand-in for ScrolledText, so I can examine
# the effects of 'yscrollcommand'.
#from tkinter import scrolledtext
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
text_out = tk.Text(frame, state=tk.DISABLED)
text_out.pack(side=tk.LEFT)
def from_scollbar_yview(*args, **kwargs):
print("\ntext_out.yview: ", args, kwargs)
return text_out.yview(*args, **kwargs)
scrollbar = tk.Scrollbar(frame, command=from_scollbar_yview)
scrollbar.pack(side=tk.LEFT, fill='y')
button = tk.Button(root, text="copy")
button.pack()
text_in = tk.Text(root)
text_in.pack()
def from_text_set(*args, **kwargs):
print("\nscrollbar.set: ", args, kwargs)
print("... and yview is: ", text_out.yview())
return scrollbar.set(*args, **kwargs)
text_out.configure(yscrollcommand = from_text_set)
def on_click(event):
y1 = text_out.yview()
print("\ncopy start: ", y1)
s = text_in.get("1.0","end-1c")
# Attempted work-around. If the explanations below had been
# correct this, though extremely hacky, would have worked.
#yc = text_out.cget("yscrollcommand")
#i = 0
#def yc_ignore_n(*args):
# nonlocal i
# print("called yscrollcommand: ", i, *args)
# if i >= 1:
# print("setting yview[0]: ", y2[0])
# text_out.yview("moveto", y2[0])
# text_out.configure(yscrollcommand = yc)
# print("yscrollcommand restored!!")
# i += 1
# return True
#if y1[0] > 0 or y1[1] < 1:
# print("implementing workaround")
# text_out.configure(yscrollcommand = yc_ignore_n)
print(f"copying text: {len(s)} chars")
text_out.configure(state=tk.NORMAL)
# I now think these explanations are incorrect:
#
# If the text height changes, 'delete' will queue a
# 'yscrollcommand' at the *front*. This *will* mess
# with the 'yview'.
text_out.delete(1.0, "end")
# If the text height changes, 'insert' will queue a
# 'yscrollcommand' at the *front* (before 'delete').
text_out.insert("end-1c", s)
text_out.configure(state=tk.DISABLED)
print("setting yview[0]: ", 0.25)
text_out.yview("moveto", 0.25)
# When the contents only slightly exceed the size of the
# text, 'yview' is set to an incorrect value. Why?
y2 = text_out.yview()
print("copy stop:", y2)
button.bind("<Button-1>", on_click)
root.mainloop()
注意解决方法已被注释掉。它只适用于 [3],所以继续粘贴一个很长的字符串。我正在使用 this Lorem Ipsum generator.
这是一个很长的字符串 [3] 的输出。我试图将视图 window 的顶部设置为 25%,但最终设置为 15%。我的代码中的注释解释了为什么会这样。
...
copy start: (0.0, 1.0)
copying text: 2623 chars
setting yview[0]: 0.25
copy stop: (0.25, 1.0)
scrollbar.set: ('0.25', '1.0') {}
... and yview is: (0.25, 1.0)
scrollbar.set: ('0.15625', '0.75625') {}
... and yview is: (0.15625, 0.75625)
这是一个稍长的字符串 [2].
的输出copy start: (0.1724137931034483, 1.0)
copying text: 2569 chars
setting yview[0]: 0.25
copy stop: (0.35135135135135137, 1.0)
scrollbar.set: ('0.35135135135135137', '1.0') {}
... and yview is: (0.35135135135135137, 1.0)
虽然我现在认为代码注释中的解释不正确,但我可以肯定它们是由yscrollcommand
被过度调用引起的。我无法在不破坏滚动条的情况下完全禁用 yscrollcommand
。我无法确定在所有排队 yscollcommand
之后设置正确 yview 的回调将 运行,也不会修复闪烁 [1].
第一个 yscrolledcommand
正在排队等待状态更改 (normal/disabled)。来自 insert
(不是 delete
)的第二个在文本视图 外部 换行时触发。可能将视图位置调整为任何换行。由于调整会导致滚动条缩小,这意味着它最初将换行视为单行,然后将换行视为多行。正常25%就是25%,但是因为调整的时候view不能移动,现在内容多了,top position的百分比就要降低。这就是导致闪烁的原因。
对于部分模糊的回绕线,这仅在底部触发。
window视图的初始配置只考虑可见的回绕线,不会引起任何闪烁。对于部分模糊的环绕线,这只考虑顶部的线。在处理回绕线时,初始和最终视图配置完全相反。
无论如何,我想证明 Tk 的做法是完全错误的。假设我有 20 条非换行,一条单行换行成 20 行,还有 20 条非换行。我想将滚动的顶部 window 设置为 25%,这意味着换行将完全可见。
在 25% 时,这是应该可见的第一行:
(12+20*2)*.25
>>> 13.0
但是 Tk 显示了第 11 行的一部分。这对应什么?
(1+20*2)*.25
>>> 10.25
10.25/(12+20*2)
>>> 0.1971153846153846
这就是 Tk 根据我的 yview
调整到:粗略的 19%。
现在假设我有 40 条非换行和一条换行成 20 行的单行。我想将卷轴的顶部 window 设置为 25%,这意味着不会显示任何换行。
我会展示计算结果,但它们几乎完全相同。唯一的区别是滚动条会闪烁。相反:
copy start: (0.0, 0.46153846153846156)
copying text: 1025 chars
setting yview[0]: 0.25
copy stop: (0.25, 0.8353658536585366)
scrollbar.set: ('0.25', '0.8353658536585366') {}
... and yview is: (0.25, 0.8353658536585366)
scrollbar.set: ('0.1971153846153846', '0.6586538461538461') {}
... and yview is: (0.1971153846153846, 0.6586538461538461)
那么我们如何解决这个问题呢?您可以尝试撤消 Tk 的数学运算,向后工作以获得将转换为您想要的值的值。但是,您仍然会闪烁。最好的解决方案是自己换行 (wrap = "none"
)。为此,您需要 window 尺寸。虽然 window 大小是根据创建时的字体大小指定的,但内部并不维护。您只能使用 winfo_width
获得 window 大小(以像素为单位)。幸运的是 Font.measure
为实例化字体提供给定字符串的像素宽度。
唯一的问题是值得吗?每次调整小部件大小时,您都必须绑定到 <Configure>
才能重新包装。这也意味着决定如何调整滚动条和显示哪个视图。