使用 Tkinter 文本索引表达式删除最后一行文本

Using Tkinter Text indexing expressions to delete last line of text

我有一个文本小部件,它在我的程序运行时显示信息。我想添加允许我用新行覆盖文本小部件的最后一行的功能。我的代码如下所示:

class TextRedirector(object):
   def __init__(self, text_widget):
      self.text_widget = text_widget
   def write(self, the_string):
      self.text_widget.configure(state="normal")
      self.text_widget.insert("end", the_string)
      self.text_widget.see(END)
      self.text_widget.configure(state="disabled")
   def overwrite(self, the_string):
      self.text_widget.configure(state="normal")
      self.text_widget.delete("end-1l linestart+1c", "end")
      self.text_widget.insert("end", the_string)
      self.text_widget.see(END)
      self.text_widget.configure(state="disabled")

How do I get the line and column of the end position of text in Tkinter? -- I have seen this post, where Bryan Oakley appears to answer my question with textwidget.delete("end-1c linestart", "end"), but when I use this, the text alternates between being placed at the end of the last line, and actually overwriting the last line. That is to say, it works half of the time, and half of the time the new text is slapped on the end of the old text. My understanding of the index expressions for the Text widget (covered tersely at http://effbot.org/tkinterbook/text.htm) 是 "end-1c linestart" 的意思类似于 "end, back one character, beginning of line" 所以我不明白为什么它会转到一行文本的末尾,而且只是每隔一次.每一步的结果看起来都像这样(每个部分都是整个文本小部件的更新版本,只有最后几行被修改):

!!!!! Settings Have Been Backed Up !!!!!

Computing coordinates...

Coordinates have been computed.

iteration: 1 / 42
-----

!!!!! Settings Have Been Backed Up !!!!!

Computing coordinates...

Coordinates have been computed.

iteration: 1 / 42iteration: 2 / 42
-----

!!!!! Settings Have Been Backed Up !!!!!

Computing coordinates...

Coordinates have been computed.
iteration: 3 / 42
-----

!!!!! Settings Have Been Backed Up !!!!!

Computing coordinates...

Coordinates have been computed.iteration: 4 / 42
-----

我尝试使用 self.text_widget.delete("end-1l linestart+1c", "end")。这几乎可行,但我最终得到

!!!!! Settings Have Been Backed Up !!!!!

Computing coordinates...

Coordinates have been computed.

iteration: 1 / 42
-----

!!!!! Settings Have Been Backed Up !!!!!

Computing coordinates...

Coordinates have been computed.

iteration: 1 / 42
iteration: 2 / 42
-----

!!!!! Settings Have Been Backed Up !!!!!

Computing coordinates...

Coordinates have been computed.

iteration: 1 / 42
iiteration: 3 / 42
-----

!!!!! Settings Have Been Backed Up !!!!!

Computing coordinates...

Coordinates have been computed.

iteration: 1 / 42
iiteration: 4 / 42

我尝试了一些其他的东西,尽我所能使用这些索引表达式,但我还没有解决问题。我尝试在覆盖函数中添加 if 语句来处理可能位于小部件末尾的文本的不同场景,例如,如果它以一个空行或两个空行结束,等等。我也没有成功。

为了全面披露,我可能会提到我正在使用这个文本小部件作为命令行打印输出的一种替代品。因此 class,TextRedirector 的名称。我不认为这对手头的问题有任何影响,但错误的假设可能首先让我来到这里......class 之后的行是这样的:

sys.stdout = TextRedirector(self.textbox)

而 self.textbox 是在定义 class 之前创建的文本小部件。

更新:我尝试保存最后一次插入文本之前的索引,并基于该索引构建一个字符串表达式以删除最后一行。结果仍然不完美。

class TextRedirector(object):
   def __init__(self, text_widget):
      self.text_widget = text_widget
      self.index_before_last_print = ""
   def write(self, the_string):
      self.index_before_last_print = self.text_widget.index("end")
      self.text_widget.configure(state="normal")
      self.text_widget.insert("end", the_string)
      self.text_widget.see(END)
      self.text_widget.configure(state="disabled")
   def overwrite(self, the_string):
      self.text_widget.configure(state="normal")
      self.text_widget.delete(self.index_before_last_print + "-1c linestart+1c", "end")
      self.index_before_last_print = self.text_widget.index("end")
      self.text_widget.insert("end", the_string)
      self.text_widget.see(END)
      self.text_widget.configure(state="disabled")

这是结果

!!!!! Settings Have Been Backed Up !!!!!

Computing coordinates...

Coordinates have been computed.

iteration: 1 / 42
-----

!!!!! Settings Have Been Backed Up !!!!!

Computing coordinates...

Coordinates have been computed.

iiteration: 2 / 42
-----

!!!!! Settings Have Been Backed Up !!!!!

Computing coordinates...

Coordinates have been computed.

iiteration: 3 / 42
-----

!!!!! Settings Have Been Backed Up !!!!!

Computing coordinates...

Coordinates have been computed.

iiteration: 4 / 42

更新: 下面描述的行为记录在 Tcl/Tk 文档中,此处:https://www.tcl.tk/man/tcl8.6/TkCmd/text.htm。具体来说,在 pathName insert 文档中说明了在小部件中最后一个换行符之后的位置插入实际上发生在该换行符之前;并且 pathName delete 文档说,不让小部件留下最终换行符的删除将被静默修改以保留最终换行符。

原回答:

我遇到了同样的问题,并且找到了适合我的解决方案。 TL;博士: Tk 文本小部件在插入时有一些令人费解的(对我来说)不规则 在结束位置删除,但如果你总是附加新内容 前导(而不是尾随)换行符,它的行为应该如您所料。 具体来说,text.delete("end-1l","end") 后跟 text.insert("end",u"\nContent") 将替换最后一行。

通过试验 wish (the Tk shell) 和 Python 2.7 中的文本小部件, 我发现:

1) 新创建的文本小部件包含换行符。你不能轻易删除 它,如果你以某种方式设法做到这一点,小部件将表现得非常 奇怪的是之后。

>>> import Tkinter as tk
>>> root = tk.Frame()
>>> t = tk.Text(root)
>>> t.grid()
>>> root.grid()
>>> t.get('1.0','end')
u'\n'
>>> t.delete('1.0','end')
>>> t.get('1.0','end')
u'\n'

嗯,这是一个惊喜! “从头开始删除所有内容”的哪一部分 到最后”没看懂吗Tk?

2) 在 "end" 处的插入实际上是在最后一个换行符之前插入的。这个 似乎与 Tk 文档冲突,这表明 "end"位置是指之后之后的字符位置 小部件中的最后一个换行符。

>>> t.insert('end',u"\nA line")
>>> t.get('1.0',"end')
u'\nA line\n'

虽然我们想在最后插入,但实际上插入了 最后的换行符之前。

>>> t.insert('end',u"\nLine 2") 
>>> t.get('1.0','end')
u'\nA line\nLine 2\n'

而且这种行为似乎是一致的。如果我们尝试删除一行怎么办?出色地 以 "intuitive" 的方式做到这一点:从 "end" 备份一行并从中删除 "end":

>>> t.delete('end-1l','end')
>>> t.get('1.0','end')
u'\nA line\n'

我们回到了以前的状态,这是个好消息!另一个插入 将插入的行放在预期的位置:

>>> t.insert('end',u"\nA new line")
>>> t.get('1.0','end')
u'\nA line\nA new line\n'

但这仅在我们使用前导换行符添加行时才按预期工作。 如果我们使用尾随换行符添加它们,文本将附加到 前一行,并且一个额外的尾随换行符被添加到 小部件:

>>> t.insert('end',u"Trailing newline\n")
>>> t.get('1.0','end')
u'\nA line\nA new lineTrailing newline\n\n'

如果您相信 Tk 文档,这不是您所期望的 - 您 期望在 "end" 处插入会在 after 处插入你的测试 最后的换行符。但是,唉,你错了。

以下完整的测试程序显示了一个文本小部件,以及一个 输入字段和两个按钮,其中一个将输入字段中的一行添加到 小部件,另一个覆盖小部件的最后一行 输入字段文本。 addLine() 和 replaceLastLine() 函数实现 这些行为以一种直截了当的方式。开头的空行 小部件是一个小烦恼,您可以通过以下方式解决 t.delete("1.0","1.0+1c"),但只有 向小部件中插入一些文本之后。

import Tkinter as tk
root = tk.Frame()
t = tk.Text(root)
t.grid(row=0,column=0,columnspan=3)
root.grid()

def addLine():
  msg = lineField.get()
  t.insert("end",u"\n{}".format(msg))

def replaceLastLine():
  msg = lineField.get()
  t.delete("end-1l","end")
  t.insert("end",u"\n{}".format(msg))

lineField = tk.Entry(root)
lineField.grid(row=1,column=0)

addBtn = tk.Button(root,text="Add line",command=addLine)
addBtn.grid(row=1,column=1)

replBtn = tk.Button(root,text="Replace line",command=replaceLastLine)
replBtn.grid(row=1,column=2)

tk.mainloop()

I have seen this post [which] appears to answer my question with textwidget.delete("end-1c linestart", "end"), but when I use this, the text alternates between being placed at the end of the last line, and actually overwriting the last line.

附加文本的原因很容易解释。如果插入以换行符结尾的行(例如:"foo\n"),则 "end-1c linestart" 指的是紧接在最后一个换行符之前的位置。因此,您的代码仅覆盖换行符,这会导致将新文本附加到旧文本。

如果您的代码不一致,有时您插入一行带有换行符的文本,有时没有 - 或者有时带有多个换行符 - 这可以解释为什么代码在不同时间似乎表现不同。

如果 所有 写入文本小部件都通过您的重定向器 class,一个非常简单的解决方案是为最后添加的数据添加一个标签。然后,您的覆盖方法可以简单地删除带有此标记的所有文本。

这是一个提供两个按钮的工作示例,一个用于附加文本,一个用于覆盖文本:

import tkinter as tk
import datetime

class TextRedirector(object):
    def __init__(self, text_widget):
        self.text_widget = text_widget
        self.text_widget.tag_configure("last_insert", background="bisque")

    def write(self, the_string):
        self.text_widget.configure(state="normal")
        self.text_widget.tag_remove("last_insert", "1.0", "end")
        self.text_widget.insert("end", the_string, "last_insert")
        self.text_widget.see("end")
        self.text_widget.configure(state="disabled")

    def overwrite(self, the_string):
        self.text_widget.configure(state="normal")
        last_insert = self.text_widget.tag_ranges("last_insert")
        self.text_widget.delete(last_insert[0], last_insert[1])
        self.write(the_string)

def overwrite():
    stdout.overwrite(str(datetime.datetime.now()) + "\n")

def append():
    stdout.write(str(datetime.datetime.now()) + "\n")

root = tk.Tk()
text = tk.Text(root)
stdout = TextRedirector(text)

append = tk.Button(root, text="append", command=append)
overwrite = tk.Button(root, text="overwrite", command=overwrite)

append.pack()
overwrite.pack()
text.pack()

root.mainloop()

您只需使用 text.delete("end-2l","end-1l") 删除文本框中写入的最后一行,然后 text.insert(INSERT,"your text here") 重写即可。