请告诉我有更好的方法来设置 ScrolledText 视图 window

Please tell me there's a better way to set the ScrolledText view window

我有这个示例代码来探索 ScrolledText 的内部工作原理,试图找出原因:

  1. 替换文本内容后设置视图window滚动条闪烁
  2. 如果文本稍微超出视图 window,则视图 window 设置为我指定的值以外的值。请注意,这是 而不是 由行舍入引起的,因为该值被多行关闭。
  3. 如果文本大大超过视图 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> 才能重新包装。这也意味着决定如何调整滚动条和显示哪个视图。