How do I only get the latest input from tkinter Text widget?

Question:

I need to get only the latest input from my text widget, and then append that character to a list.
I am using
Text.get(1.0,'end-1c')
, and it does not work because the loop constantly gets all the input, instead of only getting the latest input when there is a new latest input.

def main_screen():
    start_time=time.time()
    tk=Tk()
    tk.title('Typing Test')
    tk.geometry('800x500')
    main_title=Label(tk,text='1 Minute Test',font=('Times New Roman',36))
    main_title.pack(pady=5)
    directions=Label(tk,text='Start Typing',font=('Times New Roman',14))
    directions.pack()
    base_text=Label(tk,text=randomizer(),bg='#E0E0EE',font=('Arial',14),wraplength=700,justify=LEFT)
    base_text.pack(pady=10)
    text_area=Text(tk,font=('Arial',14),width=63,height=7,wrap='word')
    text_area.pack()
    tk.update()
#WPM Calculation
    target_text=randomizer()
    typed_text=[]
    wpm=0
    errors=0
    while True:
        tk.update()
        time_elapsed=max(time.time()-start_time,1)
        wpm=round((len(typed_text)/60)/5)
        if time_elapsed>=60:
            break
#Problem Section
        key=text_area.get(1.0,'end-1c')
        typed_text.append(key)
        for x in typed_text:
            if x != target_text:
                errors += 1

Alternatively, I tried using a variable in place of the 1.0 in .get, that would increase by one with each iteration of the loop. Next, I tried a try/except command, and put the #Problem Section into a function. I tried calling that function by binding the text area to

'<Key>'
'<KeyPress>'
'<KeyRelease>'

None of these attempts work. I used a print statement to see what those variables are with each iteration of the loop, and using the first method, it just keeps making a longer and longer string that repeats constantly, instead of updating with each new character. Trying the other ways I just got nothing, no output, but no error either. I am completely stuck, and don’t know what else to try.

Asked By: Thomas

||

Answers:

You can bind the text_area with a <KeyPress> event, but you need to pass the list typed_text as an argument so you can append the presses.

So you should do something like this:

text_area.bind("<KeyPress>", lambda _: getKey(_, typed_text))
    while True:
        tk.update()
        time_elapsed = max(time.time() - start_time, 1)
        wpm = round((len(typed_text) / 60) / 5)
        if time_elapsed >= 60:
            break
        # Problem Section
        for x in typed_text:
            if x != target_text:
                errors += 1


def getKey(event, list):
    list.append(event.char)
    print(list)
Answered By: DonPre

The text widget supports something called a "mark", which is like a bookmark. You can put a mark anywhere in the text and use it just like a normal index.

Assuming that data is only ever appended to the end of the widget, the simplest solution is to fetch a block of data and then move the mark to the end of the text that you fetched. The next time you fetch data, start at that mark.

Marks have something called "gravity" that defines which character the mark sticks to. For example, if the gravity is "left" and you set it to character "2.2", the mark will always stay adjacent to the character at index "2.2". If the gravity is "right", it will be stuck at the character following index "2.2" (eg: "2.3" or "3.0")

Here’s a contrived example that will print only the latest additions to a text widget every five seconds, by tracking the last position that was used to fetch the data.

import tkinter as tk

def get_new_text():
    data = text.get("last", "end-1c")
    print(f"new data: >>>{data}<<<")
    text.mark_set("last", "end-1c")
    root.after(5000, get_new_text)

root = tk.Tk()
text = tk.Text(root, wrap="word")
text.pack(fill="both", expand=True)
text.mark_set("last", "1.0")
text.mark_gravity("last", "left")

root.after(5000, get_new_text)

root.mainloop()
Answered By: Bryan Oakley
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.