Using Python Tkinter to display the number of lines in the Text text box

Recently, I used tkinter to create a text editor. After a long time, I finally studied the effect of displaying the number of text lines like pycharm, as shown in the figure:

Put the source code directly, and then explain it in detail

from tkinter import *
from tkinter import scrolledtext
from threading import Thread, RLock


class Main(Tk):
    def __init__(self):
        super().__init__()
        self.thread_lock = RLock()
        self.txt = ""
        self._main()

    def _main(self):
        self.resizable(True, True)
        self.geometry("800x600")
        self.edit_frame = Canvas(self, height=600, width=800,
                                 bg="white", highlightthickness=0)
        self.edit_frame.pack()
        self.line_text = Text(self.edit_frame, width=7, height=600, spacing3=5,
                              bg="#DCDC ", BD = 0, font =" light ", 14), takefocus = 0, state =" disabled ",
                              cursor="arrow")
        self.line_text.pack(side="left", expand="no")
        self.update()
        self.edit_text = scrolledtext.ScrolledText(self.edit_frame, height=1, wrap="none", spacing3=5,
                                                   width=self.winfo_width() - self.line_text.winfo_width(), bg="white",
                                                   bd=0, font=("Isoline (Light)", 14), undo=True, insertwidth=1)
        self.edit_text.vbar.configure(command=self.scroll)
        self.edit_text.pack(side="left", fill="both")
        self.line_text.bind("<MouseWheel>", self.wheel)
        self.edit_text.bind("<MouseWheel>", self.wheel)
        self.edit_text.bind("<Control-v>", lambda e: self.get_txt_thread())
        self.edit_text.bind("<Control-V>", lambda e: self.get_txt_thread())
        self.edit_text.bind("<Key>", lambda e: self.get_txt_thread())
        self.show_line()

    def wheel(self, event):
        self.line_text.yview_scroll(int(-1 * (event.delta / 120)), "units")
        self.edit_text.yview_scroll(int(-1 * (event.delta / 120)), "units")
        return "break"

    def scroll(self, *xy):
        self.line_text.yview(*xy)
        self.edit_text.yview(*xy)

    def get_txt_thread(self):
        Thread(target=self.get_txt).start()

    def get_txt(self):
        self.thread_lock.acquire()
        if self.txt != self.edit_text.get("1.0", "end")[:-1]:
            self.txt = self.edit_text.get("1.0", "end")[:-1]
            self.show_line()
        else:
            self.thread_lock.release()

    def show_line(self):
        sb_pos = self.edit_text.vbar.get()
        self.line_text.configure(state="normal")
        self.line_text.delete("1.0", "end")
        txt_arr = self.txt.split("\n")
        if len(txt_arr) == 1:
            self.line_text.insert("1.1", " 1")
        else:
            for i in range(1, len(txt_arr) + 1):
                self.line_text.insert("end", " " + str(i))
                if i != len(txt_arr):
                    self.line_text.insert("end", "\n")
        if len(sb_pos) == 4:
            self.line_text.yview_moveto(0.0)
        elif len(sb_pos) == 2:
            self.line_text.yview_moveto(sb_pos[0])
            self.edit_text.yview_moveto(sb_pos[0])
        self.line_text.configure(state="disabled")
        try:
            self.thread_lock.release()
        except RuntimeError:
            pass


if __name__ == "__main__":
    run = Main()
    run.mainloop()

Let's talk about the main idea first:
First, two Text are created in the _main() function, one for displaying the number of lines (line_text) and one for typing (edit_text).
Every time you enter a character in edit_text, you will get the content in the Text box (because get() automatically adds one character to the end of the string obtained by getting the Text content, so [: - 1] is used to intercept the last two characters), and then use the string split function to separate the "\ n" carriage return. At this time, the length of the list is how many lines there are.
Then use a loop to add a number to line_text to display the number of lines.

Details (key to implementation):
1. I changed the cursor parameter of line_text to "arrow" to make the mouse move to the normal state instead of the input state. Set takefocus to 0 to prevent line_text from getting focus. Set state to "disabled" and then set it to "normal" when adding numbers to prevent misinput in line_text.

2. It can be found that I bound a Key keyboard event with edit.text, and the executed function is get_txt_thread(), which starts the thread of self.get_txt function instead of directly setting the execution to self.get_txt() , that's because I found that the input is after the execution of the event, which means that when a Key is pressed, the keyboard bound event is executed first, and then something is entered into the Text box. Suppose I press 1 and then 2, 12 is displayed in the Text box, but only 1 is actually obtained in the Text. Therefore, I use the multi-threaded method to hard core the execution of the event and the input synchronously, In this way, you can get the content in the Text box correctly every time you enter

3. It is not enough to just add a multi thread. The thread lock should be added before executing the code in the thread, and then untied after execution. In this way, the thread can be executed in order to prevent line_text from displaying errors when multiple characters are input at one time with the input method

4. To realize the simultaneous control of line_text and edit_text by the roller, I use the ideas provided in this article: tkinter implements a scroll bar to control two components at the same time

So this article is here to introduce. If you think it's good, let's give it a key three times!

Tags: Python Pycharm Tkinter

Posted on Tue, 21 Sep 2021 19:46:55 -0400 by Twister1004