Как сделать нумерацию строк для текстового поля в tkinter?

У меня есть текстовое поле со скролбаром:

from tkinter import *

root=Tk()

scroll=ttk.Scrollbar(root)
scroll.pack(fill=Y,side=RIGHT)

text=Text(root,yscrollcommand=scroll.set)
text.pack(fill=BOTH)

scroll.config(command=text.yview)

root.mainloop()

Вот результат:

результат

И я хотел слева от текстового поля сделать поле для нумерации строк в текстовом поле. Я пробовал и Label и canvas , но ничего не работает. Можете подсказать как сделать нумерацию строк?


Ответы (2 шт):

Автор решения: insolor

Сделал немного не так как планировал: просто два текстовых поля и скролбар в гриде.

import tkinter as tk
from tkinter import ttk

root = tk.Tk()

# Перенос строк отключен, иначе нумерация будет работать некорректно
numbers = tk.Text(root, width=4, bg='lightgray', state=tk.DISABLED, relief=tk.FLAT)
numbers.grid(row=0, column=0, sticky='NS')

scroll = ttk.Scrollbar(root)
scroll.grid(row=0, column=2, sticky='NS')


def on_yscrollcommand(*args):
    scroll.set(*args)  # Синхронизация скролбара с текстовым полем
    numbers.yview_moveto(args[0])  # Синхронизация поля с номерами с текстовым полем


text = tk.Text(root, yscrollcommand=on_yscrollcommand, wrap=tk.NONE)
text.grid(row=0, column=1, sticky='NSWE')


def scroll_command(*args):
    # Движение скролбара управляет отображением текста в обоих текстовых полях
    text.yview(*args)
    numbers.yview(*args)


scroll.config(command=scroll_command)


def insert_numbers():
    count_of_lines = text.get(1.0, tk.END).count('\n') + 1
    
    numbers.config(state=tk.NORMAL)
    numbers.delete(1.0, tk.END)
    numbers.insert(1.0, '\n'.join(map(str, range(1, count_of_lines))))
    numbers.config(state=tk.DISABLED)


insert_numbers()


def on_edit(event):
    # Срабатывает при изменениях в текстовом поле
    insert_numbers()
    text.edit_modified(0)  # Сбрасываем флаг изменения текстового поля


text.bind('<<Modified>>', on_edit)

# Нужно чтобы текстовое поле автоматически меняло размер при изменении размера окна
root.grid_columnconfigure(1, weight=1)
root.grid_rowconfigure(0, weight=1)


# Для примера загружаем саму программу в редактор
import sys

with open(sys.argv[0]) as file:
    text.insert(1.0, file.read())

root.mainloop()

Скриншот

→ Ссылка
Автор решения: Victor VosMottor thanks Monica

source @Bryan Oakley:

import tkinter as tk


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)


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 


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()

Скриншот

→ Ссылка