Tkinter добавляет номер строки в текстовый виджет - программирование
Подтвердить что ты не робот

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)

К сожалению, это дает некоторые странные числа в кадре. Есть ли более простое решение? Как подойти к этому.

Thnks

4b9b3361

Ответ 1

У меня есть относительно надежное решение, но оно сложное и, вероятно, будет трудно понять, потому что оно требует некоторых знаний о том, как работает Tkinter и базовый текстовый виджет tcl/tk. Я представлю его здесь как полное решение, которое вы можете использовать как есть, потому что я думаю, что это иллюстрирует уникальный подход, который работает достаточно хорошо.

Обратите внимание, что это решение работает независимо от того, какой шрифт вы используете, и используете ли вы разные шрифты в разных строках, встроенные виджеты и т.д.

Импорт Tkinter

Прежде чем мы начнем, следующий код предполагает, что tkinter импортируется, как это, если вы используете python 3.0 или выше:

import tkinter as tk

... или это для python 2.x:

import tkinter as tk

Виджет номера строки

Разрешить отображение номеров строк. То, что мы хотим сделать, это использовать холст, чтобы мы могли точно позиционировать цифры. Мы создадим пользовательский класс и дадим ему новый метод с именем 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)

Если вы связываете это с текстовым виджемом, а затем вызываете метод 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        

Объединяя все вместе

Наконец, вот примерная программа, которая использует эти два класса:

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

Ответ 2

Я видел пример на сайте под названием unpythonic, но он предполагает, что высота строки txt составляет 6 пикселей.

Для сравнения:

# предположить, что каждая строка не менее 6 пикселей высокая. step = 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))
    ...

Ответ 3

Здесь моя попытка сделать то же самое. Я попробовал ответить Брайан Оукли выше, он выглядит и отлично работает, но он идет по цене с производительностью. Каждый раз, когда я загружаю большое количество строк в виджет, для этого требуется много времени. Чтобы обойти это, я использовал обычный виджет Text для рисования номеров строк, вот как я это сделал:

Создайте виджет "Текст" и поместите его слева от основного текстового виджета, который вы добавляете для строк, позвоните ему textarea. Убедитесь, что вы также используете тот же шрифт, который вы используете для textarea:

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

Добавьте тег для правильного выравнивания всех строк, добавленных в виджет номеров строк, позвоните ему 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)

Теперь мы готовы создать uniscrollbar:

    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)

Voila! Теперь у вас очень легкий текстовый виджет с номерами строк:

введите описание изображения здесь

Ответ 4

Существует очень простой метод, который я использовал на основе ответа Брайана Окли выше. Вместо прослушивания любых сделанных изменений просто "обновите" виджет, используя метод 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)