Tkinter添加行号到文本小部件

试图学习tkinter和python。 我想在相邻的框架中显示文本小部件的行号

from Tkinter import * root = Tk() txt = Text(root) txt.pack(expand=YES, fill=BOTH) frame= Frame(root, width=25) # frame.pack(expand=NO, fill=Y, side=LEFT) root.mainloop() 

我曾经在一个名为unpythonic的网站上看过一个例子,但是它假定txt的行高是6个像素。

我正在尝试这样的事情:

1)将Any-KeyPress事件绑定到返回发生按键的行的函数:

 textPad.bind("<Any-KeyPress>", linenumber) def linenumber(event=None): line, column = textPad.index('end').split('.') #creating line number toolbar try: linelabel.pack_forget() linelabel.destroy() lnbar.pack_forget() lnbar.destroy() except: pass lnbar = Frame(root, width=25) for i in range(0, len(line)): linelabel= Label(lnbar, text=i) linelabel.pack(side=LEFT) lnbar.pack(expand=NO, fill=X, side=LEFT) 

不幸的是,这是在框架上给一些奇怪的数字。 有一个更简单的解决scheme? 如何解决这个问题。

thnks

我有一个相对简单的解决scheme,但它很复杂,而且可能很难理解,因为它需要一些关于Tkinter和底层tcl / tk文本小部件的知识。 我将把它作为一个完整的解决scheme呈现在你的面前,因为我认为它说明了一个很好的独特方法。

请注意,无论您使用何种字体,以及是否在不同的行上使用不同的字体,embedded小部件等等,此解决scheme都能正常工作。

导入Tkinter

在我们开始之前,下面的代码假定tkinter是像这样导入的,如果你使用的是python 3.0或更高版本:

 import tkinter as tk 

…或者这个,对于python 2.x:

 import Tkinter as tk 

行号小部件

我们来处理行号的显示。 我们想要做的是使用canvas,以便我们可以精确地定位数字。 我们将创build一个自定义类,并为其提供一个名为redraw的新方法,将重绘相关文本小部件的行号。 我们还给它一个方法attach ,用于attach一个文本小部件与这个小部件相关联。

这个方法利用了这个事实,即文本部件本身可以通过dlineinfo方法准确地告诉我们一行文本的开始和结束。 这可以准确地告诉我们在canvas上绘制行号的位置。 它也利用了这样一个事实,如果一条线不可见, dlineinfo返回None ,我们可以使用该线来知道何时停止显示线号。

 class TextLineNumbers(tk.Canvas): def __init__(self, *args, **kwargs): tk.Canvas.__init__(self, *args, **kwargs) self.textwidget = None def attach(self, text_widget): self.textwidget = text_widget def redraw(self, *args): '''redraw line numbers''' self.delete("all") i = self.textwidget.index("@0,0") while True : dline= self.textwidget.dlineinfo(i) if dline is None: break y = dline[1] linenum = str(i).split(".")[0] self.create_text(2,y,anchor="nw", text=linenum) i = self.textwidget.index("%s+1line" % i) 

如果将它与文本小部件关联,然后调用redraw方法,它应该显示行号就好了。

这个工作,但有一个致命的缺陷:你必须知道什么时候调用redraw 。 你可以创build一个绑定,在每次按键时都触发,但是你也必须在鼠标button上触发,而且你必须处理用户按下键并使用自动重复function等的情况。行号也需要如果窗口长大或缩小或者用户滚动,就要重新绘制,所以我们陷入了一个试图找出可能导致数字改变的每一个可能事件的兔子洞。

还有另外一个解决scheme,就是让文本小部件在发生“变化”的时候触发一个事件。 不幸的是,文本小部件没有直接的支持。 但是,我们可以编写一些Tcl代码来拦截对文本小部件的更改,并为我们生成一个事件。 在“ 绑定到光标移动不会改变INSERT标记 ”的问题的答案中,我提供了一个类似的解决scheme,显示了如何让文本小部件在发生变化时调用callback。 这一次,而不是callback,我们会产生一个事件,因为我们的需求是有点不同。

自定义文本类

这是一个创build自定义文本小部件的类,每当插入或删除文本或滚动视图时,都会生成一个<<Change>>事件。

 class CustomText(tk.Text): def __init__(self, *args, **kwargs): tk.Text.__init__(self, *args, **kwargs) self.tk.eval(''' proc widget_proxy {widget widget_command args} { # call the real tk widget command with the real args set result [uplevel [linsert $args 0 $widget_command]] # generate the event for certain types of commands if {([lindex $args 0] in {insert replace delete}) || ([lrange $args 0 2] == {mark set insert}) || ([lrange $args 0 1] == {xview moveto}) || ([lrange $args 0 1] == {xview scroll}) || ([lrange $args 0 1] == {yview moveto}) || ([lrange $args 0 1] == {yview scroll})} { event generate $widget <<Change>> -when tail } # return the result from the real widget command return $result } ''') self.tk.eval(''' rename {widget} _{widget} interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget} '''.format(widget=str(self))) 

把它放在一起

最后,这里是一个使用这两个类的示例程序:

 class Example(tk.Frame): def __init__(self, *args, **kwargs): tk.Frame.__init__(self, *args, **kwargs) self.text = CustomText(self) self.vsb = tk.Scrollbar(orient="vertical", command=self.text.yview) self.text.configure(yscrollcommand=self.vsb.set) self.text.tag_configure("bigfont", font=("Helvetica", "24", "bold")) self.linenumbers = TextLineNumbers(self, width=30) self.linenumbers.attach(self.text) self.vsb.pack(side="right", fill="y") self.linenumbers.pack(side="left", fill="y") self.text.pack(side="right", fill="both", expand=True) self.text.bind("<<Change>>", self._on_change) self.text.bind("<Configure>", self._on_change) self.text.insert("end", "one\ntwo\nthree\n") self.text.insert("end", "four\n",("bigfont",)) self.text.insert("end", "five\n") def _on_change(self, event): self.linenumbers.redraw() 

…当然,在文件末尾加上这个来引导它:

 if __name__ == "__main__": root = tk.Tk() Example(root).pack(side="top", fill="both", expand=True) root.mainloop() 

我曾经在一个名为unpythonic的网站上看过一个例子,但是它假定txt的行高是6个像素

比较:

#假设每行至less有6个像素
步骤= 6

step – 多长时间(以像素计)检查新行的文本小部件。 如果文本小部件中的行高度为30像素,则此程序将执行5次检查并仅绘制一个数字。
如果字体非常小,可以将其设置为<6。
有一个条件: text小部件中的所有符号必须使用一种字体,绘制数字的小部件必须使用相同的字体。

 # http://tkinter.unpythonic.net/wiki/A_Text_Widget_with_Line_Numbers class EditorClass(object): ... self.lnText = Text(self.frame, ... state='disabled', font=('times',12)) self.lnText.pack(side=LEFT, fill='y') # The Main Text Widget self.text = Text(self.frame, bd=0, padx = 4, font=('times',12)) ... 

这是我做同样的事情的尝试。 我试过上面的Bryan Oakley的答案,它的外观和工作效果都很好,但是性价比很高。 每次我将大量的行加载到小部件中时,都需要很长时间才能完成。 为了解决这个问题,我使用了一个普通的Text小部件来绘制行号,下面是我做的:

创build“文本”小部件并将其网格化到要添加行的主文本小部件的左侧,我们将其称为textarea 。 请确保您也使用您用于textarea的相同字体:

 self.linenumbers = Text(self, width=3) self.linenumbers.grid(row=__textrow, column=__linenumberscol, sticky=NS) self.linenumbers.config(font=self.__myfont) 

添加一个标签,将所有添加到行号窗口小部件的行右alignment,我们称之为line

 self.linenumbers.tag_configure('line', justify='right') 

禁用小部件,以使其不能被用户编辑

 self.linenumbers.config(state=DISABLED) 

现在棘手的部分是添加一个滚动条,我们称之为uniscrollbar来控制主文本部件以及行号文本部件。 为了做到这一点,我们首先需要两个方法,一个被滚动条调用,然后可以更新两个文本小部件以反映新的位置,另一个在文本区域滚动时被调用,这将更新滚动条:

 def __scrollBoth(self, action, position, type=None): self.textarea.yview_moveto(position) self.linenumbers.yview_moveto(position) def __updateScroll(self, first, last, type=None): self.textarea.yview_moveto(first) self.linenumbers.yview_moveto(first) self.uniscrollbar.set(first, last) 

现在我们准备创builduniscrollbar

  self.uniscrollbar= Scrollbar(self) self.uniscrollbar.grid(row=self.__uniscrollbarRow, column=self.__uniscrollbarCol, sticky=NS) self.uniscrollbar.config(command=self.__scrollBoth) self.textarea.config(yscrollcommand=self.__updateScroll) self.linenumbers.config(yscrollcommand=self.__updateScroll) 

瞧! 您现在有一个非常轻量级的文本小部件,包含行号:

在这里输入图像描述

根据Bryan Oakley的回答,有一个非常简单的方法。 只需使用self.after()方法(它在几毫秒之后调度一个呼叫self.after() “刷新”小部件,而不是侦听所做的任何更改。 非常简单的做法。 在这种情况下,我将文本小部件在瞬间附加,但如果需要,可以稍后再做。

 class TextLineNumbers(tk.Canvas): def __init__(self, textwidget, *args, **kwargs): tk.Canvas.__init__(self, *args, **kwargs) self.textwidget = textwidget self.redraw() def redraw(self, *args): '''redraw line numbers''' self.delete("all") i = self.textwidget.index("@0,0") while True : dline= self.textwidget.dlineinfo(i) if dline is None: break y = dline[1] linenum = str(i).split(".")[0] self.create_text(2,y,anchor="nw", text=linenum) i = self.textwidget.index("%s+1line" % i) # Refreshes the canvas widget 30fps self.after(30, self.redraw)