Tkinter在文本小部件中添加行号

bha*_*arc 16 python 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()
Run Code Online (Sandbox Code Playgroud)

我在一个名为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)
Run Code Online (Sandbox Code Playgroud)

不幸的是,这会在帧上给出一些奇怪的数字.有更简单的解决方案吗?怎么解决这个问题?

Bry*_*ley 30

我有一个相对简单的解决方案,但它很复杂,可能很难理解,因为它需要了解Tkinter和底层tcl/tk文本小部件如何工作.我将在这里作为一个完整的解决方案呈现,您可以按原样使用,因为我认为它说明了一种非常有效的独特方法.

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

导入Tkinter

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

import tkinter as tk
Run Code Online (Sandbox Code Playgroud)

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

import Tkinter as tk
Run Code Online (Sandbox Code Playgroud)

行号小部件

让我们来解决行号的显示.我们想要做的是使用画布,以便我们可以精确定位数字.我们将创建一个自定义类,并为其指定一个新方法redraw,该方法将重绘相关文本小部件的行号.我们还为它提供了一种方法attach,用于将文本小部件与此小部件相关联.

此方法利用了以下事实:文本窗口小部件本身可以通过该dlineinfo方法准确地告诉我们文本行的开始和结束位置.这可以准确地告诉我们在画布上绘制行号的位置.它还利用了如果一条线不可见则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)
Run Code Online (Sandbox Code Playgroud)

如果将此与文本小部件相关联,然后调用该redraw方法,则应该显示行号.

自动更新行号

这有效,但有一个致命的缺陷:你必须知道什么时候打电话redraw.您可以创建一个在每次按键时触发的绑定,但您还必须触发鼠标按钮,并且您必须处理用户按下键并使用自动重复功能等的情况.行号也需要如果窗口增长或缩小或者用户滚动,则重新绘制,因此我们陷入了一个试图找出可能导致数字发生变化的每个可能事件的兔子洞.

还有另一种解决方案,即只要有变化,文本小部件就会触发事件.不幸的是,文本小部件没有直接支持通知程序的更改.为了解决这个问题,我们可以使用代理来拦截对文本小部件的更改并为我们生成一个事件.

在回答" 绑定到光标移动不会改变INSERT标记 "的问题时,我提供了一个类似的解决方案,它展示了如何让文本小部件在发生变化时调用回调.这一次,我们将生成一个事件,而不是回调,因为我们的需求有点不同.

自定义文本类

这是一个创建自定义文本小部件的类,<<Change>>无论何时插入或删除文本,或者滚动视图,都会生成事件.

class CustomText(tk.Text):
    def __init__(self, *args, **kwargs):
        tk.Text.__init__(self, *args, **kwargs)

        # create a proxy for the underlying widget
        self._orig = self._w + "_orig"
        self.tk.call("rename", self._w, self._orig)
        self.tk.createcommand(self._w, self._proxy)

    def _proxy(self, *args):
        # let the actual widget perform the requested action
        cmd = (self._orig,) + args
        result = self.tk.call(cmd)

        # generate an event if something was added or deleted,
        # or the cursor position changed
        if (args[0] in ("insert", "replace", "delete") or 
            args[0:3] == ("mark", "set", "insert") or
            args[0:2] == ("xview", "moveto") or
            args[0:2] == ("xview", "scroll") or
            args[0:2] == ("yview", "moveto") or
            args[0:2] == ("yview", "scroll")
        ):
            self.event_generate("<<Change>>", when="tail")

        # return what the actual widget returned
        return result        
Run Code Online (Sandbox Code Playgroud)

把它们放在一起

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

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()
Run Code Online (Sandbox Code Playgroud)

...当然,在文件的末尾添加它以引导它:

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