如何在中显示行号tkinter.文本小装置?

2024-09-22 16:30:21 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在写我自己的代码编辑器,我希望它在左边有编号的行。基于this answer我编写了以下示例代码:

#!/usr/bin/env python3

import tkinter


class CodeEditor(tkinter.Frame):
    def __init__(self, root):
        tkinter.Frame.__init__(self, root)
        # Line numbers widget
        self.__line_numbers_canvas = tkinter.Canvas(self, width=40, bg='#555555', highlightbackground='#555555', highlightthickness=0)
        self.__line_numbers_canvas.pack(side=tkinter.LEFT, fill=tkinter.Y)

        self.__text = tkinter.Text(self)
        self.__text['insertbackground'] = '#ffffff'
        self.__text.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=True)

    def __update_line_numbers(self):
        self.__line_numbers_canvas.delete("all")
        i = self.__text.index('@0,0')
        self.__text.update()                    #FIX: adding line
        while True:
            dline = self.__text.dlineinfo(i)
            if dline:
                y = dline[1]
                linenum = i[0]
                self.__line_numbers_canvas.create_text(1, y, anchor="nw", text=linenum, fill='#ffffff')
                i = self.__text.index('{0}+1line'.format(i))  #FIX
            else:
                break

    def load_from_file(self, path):
        self.__text.delete('1.0', tkinter.END)
        f = open(path, 'r')
        self.__text.insert('0.0', f.read())
        f.close()
        self.__update_line_numbers()


class Application(tkinter.Tk):
    def __init__(self):
        tkinter.Tk.__init__(self)
        code_editor = CodeEditor(self)
        code_editor.pack(fill=tkinter.BOTH, expand=True)
        code_editor.load_from_file(__file__)

    def run(self):
        self.mainloop()


if __name__ == '__main__':
    app = Application()
    app.run()

不幸的是,__update_line_numbers内部出了问题。这个方法应该在我的Canvas小部件上自上而下写行号,但它只打印第一行(1)的行号,然后退出。为什么?在


Tags: textselftrueinittkinterdeflinecode
2条回答

问题的根本原因是文本尚未在屏幕上绘制,因此调用dlineinfo将不会返回任何有用的内容。在

如果在绘制行号之前添加对self.update()的调用,那么代码的工作效果会更好一些。它不会完美地工作,因为你还有其他缺陷。更好的是,当GUI空闲时,或者在可见性事件或类似事件上调用函数。一个好的经验法则是永远不要打电话给update,除非你明白为什么不应该打电话给update()。然而,在这种情况下,它是相对无害的。在

另一个问题是,您一直在向i追加内容,但在写入画布时总是使用i[0]。当您到达第2行时,i将是“1.0+1line”。第三行是“1.0+1line+1line”,以此类推。第一个字符总是“1”。在

您应该做的是要求tkinter将修改后的i转换为规范索引,并将其用于行号。例如:

i = self.__text.index('{0}+1line'.format(i))

这将把“1.0+1line”转换为“2.0”,“2.0+1line”转换为“3.0”,依此类推。在

根本问题是在返回runloop之前调用dlineinfo,因此文本还没有布局。在

the docs解释:

This method only works if the text widget is updated. To make sure this is the case, you can call the update_idletasks method first.

与往常一样,要获得更多信息,您必须求助于Tcl docs for the underlying object,它基本上告诉您,文本小部件在更新之前可能无法正确判断哪些字符是可见的或不可见的,在这种情况下,它可能返回None,这不是因为任何问题,而是因为,就它而言,你要的是屏幕外的东西。在

测试是否存在问题的一个好方法是在调用dlineinfo(i)之前调用self.__text.see(i)。如果它改变了dlineinfo的结果,这就是问题所在。(或者,如果不是这样的话,至少是与之相关的东西,不管出于什么原因,Tk会在第1行离开屏幕后思考所有事情。)

但是在这种情况下,即使调用update_idletasks也不起作用,因为它不仅仅是更新需要更新的行信息,而是首先安排文本。您需要做的是显式地延迟这个调用。例如,将这一行添加到load_from_file的底部,现在它可以工作了:

self.__text.after(0, self.__update_line_numbers)

您也可以在调用self.__update_line_numbers()内联之前调用self.__text.update(),我认为这应该行得通。在


顺便说一句,这将真正帮助您在调试器下运行它,或者在循环的顶部添加一个print(i, dline),这样您就可以看到您得到了什么,而不仅仅是猜测。在

另外,仅仅增加一个linenumber并使用'{}.0'.format(linenumber)而不是创建像@0,0+1line+1line+1line这样(至少对我来说)不起作用的复杂索引不是更容易吗。您可以调用Text.index()将任何索引转换为规范格式,但是为什么要这么困难呢?你知道你想要的是1.02.03.0等等,对吗?在

相关问题 更多 >