Python Forum

Full Version: Line numbers in Text widget
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I've been looking at adding line numbers to a Text widget. There are several examples floating around the internet, all a bit different in most cases.

I found one that seemed fairly simple to implement as I have only been working with Python for a couple of months.

The code shown below almost works 100% but needs two modifications and this is where I need some help/guidance:

(1) when the app starts up, the line numbers are not filled out...until you press a key.

(2) when a key has been pressed and the line numbers are filled out, scrolling the text down does not change the line numbers. They are always what you see after the first key press. Obviously the line numbers should scroll along with the text but I am not sure how to do that.

Thank you for any help...

import tkinter as tk

class LineNumbers(tk.Text):
    def __init__(self, master, text_widget, **kwargs):
        super().__init__(master, **kwargs)

        self.text_widget = text_widget
        self.text_widget.bind('<KeyPress>', self.on_key_press)

        self.insert(1.0, '1')
        self.configure(state='disabled')

    def on_key_press(self, event=None):
        final_index = str(self.text_widget.index(tk.END))
        num_of_lines = final_index.split('.')[0]
        line_numbers_string = "\n".join(str(no + 1) for no in range(int(num_of_lines)))
        width = len(str(num_of_lines))

        self.configure(state='normal', width=width)
        self.delete(1.0, tk.END)
        self.insert(1.0, line_numbers_string)
        self.configure(state='disabled')

if __name__ == '__main__':
    w = tk.Tk()
    t = tk.Text(w)
    l = LineNumbers(w, t, width=2)
    t.insert('1.0', 'a\n' \
                    'b\n' \
                    'c\n' \
                    'd\n' \
                    'e\n' \
                    'f\n' \
                    'g\n' \
                    'h\n' \
                    'i\n' \
                    'j\n' \
                    'k\n' \
                    'l\n' \
                    'm\n' \
                    'n\n' \
                    'o\n' \
                    'p\n' \
                    'q\n' \
                    'r\n' \
                    's\n' \
                    't\n' \
                    'u\n' \
                    'v\n' \
                    'w\n' \
                    'x\n' \
                    'y\n' \
                    'z\n' \
                    '1\n' \
                    '2\n' \
                    '3\n' \
                    '4\n' \
                    '5\n' \
                    '6\n' \
                    '7\n' \
                    '8\n' \
                    '9\n')
    l.pack(side=tk.LEFT)
    t.pack(side=tk.LEFT, expand=1)
    w.mainloop()
This version seems to work
import tkinter as tk
 
class LineNumbers(tk.Text):
    def __init__(self, master, text_widget, **kwargs):
        super().__init__(master, **kwargs)
 
        self.text_widget = text_widget
        self.text_widget.bind('<KeyRelease>', self.on_key_release)
 
        self.insert(1.0, '1')
        self.configure(state='disabled')
 
    def on_key_release(self, event=None):
        p, q = self.text_widget.index("@0,0").split('.')
        p = int(p)
        final_index = str(self.text_widget.index(tk.END))
        num_of_lines = final_index.split('.')[0]
        line_numbers_string = "\n".join(str(p + no) for no in range(int(num_of_lines)))
        width = len(str(num_of_lines))
 
        self.configure(state='normal', width=width)
        self.delete(1.0, tk.END)
        self.insert(1.0, line_numbers_string)
        self.configure(state='disabled')
 
if __name__ == '__main__':
    w = tk.Tk()
    t = tk.Text(w)
    l = LineNumbers(w, t, width=2)
    t.insert('1.0', 'a\n' \
                    'b\n' \
                    'c\n' \
                    'd\n' \
                    'e\n' \
                    'f\n' \
                    'g\n' \
                    'h\n' \
                    'i\n' \
                    'j\n' \
                    'k\n' \
                    'l\n' \
                    'm\n' \
                    'n\n' \
                    'o\n' \
                    'p\n' \
                    'q\n' \
                    'r\n' \
                    's\n' \
                    't\n' \
                    'u\n' \
                    'v\n' \
                    'w\n' \
                    'x\n' \
                    'y\n' \
                    'z\n' \
                    '1\n' \
                    '2\n' \
                    '3\n' \
                    '4\n' \
                    '5\n' \
                    '6\n' \
                    '7\n' \
                    '8\n' \
                    '9\n')
    l.pack(side=tk.LEFT)
    t.pack(side=tk.LEFT, expand=1)
    w.mainloop()
Thanks for that. Can you explain what your lines 14 and 15 are doing?
I added some code to improve it and noted some issues:

(1) Here is a link to a test file with 200 lines, with each line numbered.

line_test.txt file

(2) when the app starts up, the text widget gets the focus right away so the line numbers are there from the start.

(3) I added a binding so you can mouse wheel (scroll) down in the text file. However, doing so causes the line numbers to get out of sync. I suspect it's because one mouse wheel click inputs more than one line down movement like the keyboard down arrow does: one line down to one key press. The wheel is multiple lines down to one wheel click. I'm not sure how to fix this, but if you can point me in the right area to look at, I might be able to figure it out. Mouse wheel clicks to how many lines move up or down is configurable, so I don't know if that plays into a fix or not. But I'll have a look and see what I can come up with if you can point me in the right direction.

(4) The PgDn/PgUp keys have a similar effect, but not for all PgDn clicks for some reason. I can hit PgDn a few times and notice the Text widget lines are out of sync with the line numbers as well as not horizontally aligned. If you can tell me what controls the horizontal alignment, I probably can make an adjustment and fix that.

Thank you again...

from tkinter import *

class LineNumbers(Text):
    def __init__(self, master, text_widget, **kwargs):
        super().__init__(master, **kwargs)

        self.text_widget = text_widget
        self.text_widget.bind('<KeyRelease>', self.on_key_release)
        self.text_widget.bind('<FocusIn>', self.on_key_release)
        self.text_widget.bind('<MouseWheel>', self.on_key_release)

        self.insert(1.0, '1')
        self.configure(state='disabled')

    def on_key_release(self, event=None):
        p, q = self.text_widget.index("@0,0").split('.')
        p = int(p)
        final_index = str(self.text_widget.index(END))
        num_of_lines = final_index.split('.')[0]
        line_numbers_string = "\n".join(str(p + no) for no in range(int(num_of_lines)))
        width = len(str(num_of_lines))

        self.configure(state='normal', width=width)
        self.delete(1.0, END)
        self.insert(1.0, line_numbers_string)
        self.configure(state='disabled')


if __name__ == '__main__':
    win = Tk()
    win.title("Line Numbers Test")
    win.geometry("800x600+1000+300")

    txt = Text(win)
    ln = LineNumbers(win, txt, width=2)

    f = open("line_test.txt", 'r')
    lines = f.readlines()
    for line in lines:
        txt.insert(END, line)
    f.close()

    ln.pack(side=LEFT, fill=BOTH)
    txt.pack(expand=True, fill=BOTH)
    txt.focus()
    win.mainloop()