我正在写我自己的代码编辑器,我希望它在左边有编号的行。基于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)的行号,然后退出。为什么?在
问题的根本原因是文本尚未在屏幕上绘制,因此调用
dlineinfo
将不会返回任何有用的内容。在如果在绘制行号之前添加对
self.update()
的调用,那么代码的工作效果会更好一些。它不会完美地工作,因为你还有其他缺陷。更好的是,当GUI空闲时,或者在可见性事件或类似事件上调用函数。一个好的经验法则是永远不要打电话给update
,除非你明白为什么不应该打电话给update()
。然而,在这种情况下,它是相对无害的。在另一个问题是,您一直在向i追加内容,但在写入画布时总是使用
i[0]
。当您到达第2行时,i
将是“1.0+1line”。第三行是“1.0+1line+1line”,以此类推。第一个字符总是“1”。在您应该做的是要求tkinter将修改后的i转换为规范索引,并将其用于行号。例如:
这将把“1.0+1line”转换为“2.0”,“2.0+1line”转换为“3.0”,依此类推。在
根本问题是在返回runloop之前调用
dlineinfo
,因此文本还没有布局。在如the docs解释:
与往常一样,要获得更多信息,您必须求助于Tcl docs for the underlying object,它基本上告诉您,文本小部件在更新之前可能无法正确判断哪些字符是可见的或不可见的,在这种情况下,它可能返回
None
,这不是因为任何问题,而是因为,就它而言,你要的是屏幕外的东西。在测试是否存在问题的一个好方法是在调用
dlineinfo(i)
之前调用self.__text.see(i)
。如果它改变了dlineinfo
的结果,这就是问题所在。(或者,如果不是这样的话,至少是与之相关的东西,不管出于什么原因,Tk会在第1行离开屏幕后思考所有事情。)但是在这种情况下,即使调用
update_idletasks
也不起作用,因为它不仅仅是更新需要更新的行信息,而是首先安排文本。您需要做的是显式地延迟这个调用。例如,将这一行添加到load_from_file
的底部,现在它可以工作了:您也可以在调用
self.__update_line_numbers()
内联之前调用self.__text.update()
,我认为这应该行得通。在顺便说一句,这将真正帮助您在调试器下运行它,或者在循环的顶部添加一个
print(i, dline)
,这样您就可以看到您得到了什么,而不仅仅是猜测。在另外,仅仅增加一个
linenumber
并使用'{}.0'.format(linenumber)
而不是创建像@0,0+1line+1line+1line
这样(至少对我来说)不起作用的复杂索引不是更容易吗。您可以调用Text.index()
将任何索引转换为规范格式,但是为什么要这么困难呢?你知道你想要的是1.0
,2.0
,3.0
等等,对吗?在相关问题 更多 >
编程相关推荐