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