Python - 如何获取Tkinter文本组件中的光标位置

1 投票
1 回答
4308 浏览
提问于 2025-04-21 05:13

我想获取Tkinter.Text中插入点的光标位置(行和列),但我遇到了一些特定的问题。

问题:我的文本编辑器项目需要为Tkinter.Text实现自定义的撤销/重做功能。我在下面的“测试一”和“测试二”中输入了相同的字符串,但由于Tkinter提供的KeyRelease事件处理程序中的列变量不一致,撤销的效果并不一致。问题似乎是因为我在第二个测试中输入得太快,导致列值不正确。你能帮我找出问题吗?

重现错误的两个测试过程:

测试一

    1. 慢慢输入这个字符串:'one two three'
    1. 按F1键查看每个单词的撤销效果。
  • 结果:运行正常。(至少对我来说是这样。强调一下:要慢慢打。)

测试二

    1. 尽可能快地输入相同的字符串:'one two three'
    1. 按F1键查看每个单词的撤销效果。
  • 结果:得到错误的列值,撤销效果不正确。(如果第一次没有看到错误,请重启脚本并重复这一步,有时快速输入是可以正常工作的。我通常最多需要尝试3到4次才能出现问题。)

问题:这是Tkinter的一个bug,还是我对Tkinter中的某些特定内容理解不够,导致我的撤销/重做记录中的列值不一致?

from Tkinter import *

class TextView(Text):

    def __init__(self, root):
        Text.__init__(self, root)

        self.history = History(self)
        self.bind("<KeyRelease>", self.keyRelease)

        # used to capture a char at a time in keyRelease.  If space char is pressed it creates a Word object and adds it to undo/redo history.
        self.word = ""

    def keyRelease(self, event):
        if event.keysym == "space":
            self.word += " "
            self.makeWordRecord()
        else:
            self.word += event.char

    def makeWordRecord(self, ):
        if len(self.word):
            index = self.index(INSERT)
            wordEvent = Word(index, self.word)
            self.history.addEvent(wordEvent)
            self.word = ""

    def undo(self, event):
        self.makeWordRecord()
        self.history.undo()

    def redo(self, event):
        self.history.redo()

class History(object):
    def __init__(self, text):
        self.text = text
        self.events = []
        self.index = -1

        # create blank document record, line one, column one, no text
        self.addEvent(Word("1.0", ""))

    def addEvent(self, event):
        if self.index +1 < len(self.events):
            self.events = self.events[:self.index +1]
        self.events.append(event)
        self.index +=1

    def undo(self):
        if self.index > 0:
            self.events[self.index].undo(self.text)
            self.index -=1

    def redo(self):
        if self.index +1  < len(self.events):
            self.index +=1
            self.events[self.index].redo(self.text)

class Word(object):
    def __init__(self, index, word):
        self.index = index
        self.word = word

    def undo(self, text):
        line = self.index.split(".")[0]
        column = int(self.index.split(".")[-1])
        startIndex = line + "." + str(column - len(self.word))
        endIndex = line + "." + str(int(column))
        text.delete(startIndex, endIndex)

    def redo(self, text):
        line = self.index.split(".")[0]
        column = int(self.index.split(".")[-1])
        startIndex = line + "." + str(column - len(self.word))
        text.insert(startIndex, self.word)

if __name__ == "__main__":
    root = Tk()
    root.geometry("400x200+0+0")

    textView = TextView(root)
    textView.pack()
    root.bind("<F1>", textView.undo)
    root.bind("<F2>", textView.redo)

    root.mainloop()

1 个回答

5

我终于搞明白了发生了什么,这和Tkinter没有关系,而是和所有的工具包有关。现在我可以诚实地说,我可以为最佳编程实践添加一些内容:

不要通过绑定到按键释放的方法来处理按键事件,而是通过按键按下的方法来处理按键。

为什么呢?

这其实不是编程的问题,而是硬件的问题。当你按下一个键时,物理键会下压。键盘里有一个弹簧会把这个键弹回去。如果这个弹簧或者你键盘的其他部分导致按键反应慢了哪怕200分之一秒,那么即使你以每分钟60个单词的速度打字,按键的顺序也可能会乱掉。这可能是因为弹簧稍微厚一点、硬一点、用得太多,或者甚至是粘粘的咖啡导致了更大的阻力。

大写字母的输入也会受到影响。按下Shift键和另一个键来输入大写字母时,这两个键必须同时按下。但是如果你在按键释放时处理按键,可能会出现Shift键比你要大写的字符键弹起来快,这样就会导致输入的小写字母。这也可能导致字符的顺序颠倒。如果你在字符被输入时检查它的位置,可能会因为这个原因得到错误的位置。

所以,还是坚持使用按键按下的方法吧。

撰写回答