waxier358
waxier358

Reputation: 5

stop for loop and wait until user type specific key in tkinter text

I am working on a typing speed app. typing speed application GUI

Until now my app can:

  1. read content of a .txt file and insert it in Base text
  2. search for " " in Base Text and save each index in a list
  3. with a for loop and a list of indexes it can change background color of each word from Base text

After changing background of first word from Base text, I want my app to wait until user will type first word in Type here and press space key. When user presses space, my loop must go forward, change background of second word, wait again for typing text in Type here and so on until it reaches the end of Base text. I can do this if I create a click button and associate it with a command that changes a value when user clicks this button. After that I can use .wait_variable() in for loop. This way, for loop will stop and wait until user types in Type here and presses button, but this is not a good design for my app.

Can someone give me a hint or another solution? Maybe my for loop is not the best approach. Thank you!

This is my code:

main.py

from typingspeed import TypingSpeedApp
my_app = TypingSpeedApp()
my_app.read_text_file()
my_app.find_space_intervals()
my_app.type_and_check()
my_app.main_loop()

typingspeed.py

import tkinter
from tkinter import *
import time
import keyboard


class TypingSpeedApp:
    def __init__(self):
        self.typing_speed_app = Tk()
        self.typing_speed_app.title("Typing Speed Application")
        self.typing_speed_app.minsize(1200, 700)
        self.typing_speed_app.resizable(False, False)
        # define background image
        self.background_image = PhotoImage(file="background_image.PNG")
        # create canvas
        self.canvas = Canvas(self.typing_speed_app, width=1200, height=700)
        self.canvas.pack(fill="both", expand=True)
        # set image in canvas
        self.canvas.create_image(0, 0, image=self.background_image, anchor="nw")
        # add a label
        self.canvas.create_text(590, 50, text="Welcome to typing speed application", font=("Helvetica", 30),
                                fill="white")
        # add a label
        self.canvas.create_text(130, 90, text="Base text:", font=("Helvetica", 20), fill="white", state=DISABLED)
        # Define Entry Boxes
        self.base_text = Text(self.typing_speed_app, font=("Helvetica", 24), width=60, height=2, bd=0,
                              wrap=tkinter.WORD)
        self.base_text_window = self.canvas.create_window(70, 120, anchor="nw", window=self.base_text)
        # add a label
        self.canvas.create_text(130, 220, text="Type here:", font=("Helvetica", 20), fill="white")
        # Define Entry Boxes
        self.type_text = Text(self.typing_speed_app, font=("Helvetica", 24), width=20, height=1, bd=0,
                              wrap=tkinter.WORD)
        self.type_text_window = self.canvas.create_window(70, 250, anchor="nw", window=self.type_text)
        # add a label
        self.canvas.create_text(510, 220, text="Timer:", font=("Helvetica", 20), fill="white")
        # Define Entry Boxes
        self.time_text = Text(self.typing_speed_app, font=("Helvetica", 24), width=5, height=1, bd=0,
                              background="green")
        self.time_text_window = self.canvas.create_window(470, 250, anchor="nw", window=self.time_text)
        # text get from base_text
        self.base_text_get_test = ""
        # this list contain indexes of spaces
        self.space_indexes = ["1.0"]

        self.space_is_not_press = tkinter.StringVar()

        self.text_from_type_text = ""

    # show gui
    def main_loop(self):
        self.typing_speed_app.mainloop()

    def read_text_file(self):
        with open('text_1.txt') as text_file:
            lines = str(text_file.readlines())
        self.base_text.insert(tkinter.END, lines[2:-2] + " ")
        self.base_text.configure(state=DISABLED)
        self.base_text_get_test = self.base_text.get(0.1, tkinter.END)

    def find_space_intervals(self):
        idx = '1.0'
        while 1:
            idx = self.base_text.search(" ", idx, stopindex=END)
            if not idx:
                break
            last_idx = '%s+%dc' % (idx, 1)
            self.space_indexes.append(idx)
            self.space_indexes.append(last_idx)
            idx = last_idx

    def wait_until_space_is_pressed(self):
        pass

    def type_and_check(self):
        for index in range(0, len(self.space_indexes) - 1, 2):
            self.base_text.tag_add("select", self.space_indexes[index], self.space_indexes[index + 1])
            self.base_text.tag_configure("select", background="gray")
            self.base_text.see(f"{self.space_indexes[index]}")
            self.typing_speed_app.update()
            time.sleep(0.02)
            #self.typing_speed_app.wait_variable(self.space_is_not_press)
            # ???????????
            # stop for loop and wait until user press space key
            # ???????????
            self.base_text.tag_remove("select", self.space_indexes[index], self.space_indexes[index + 1])

and background_image enter image description here

Upvotes: 0

Views: 88

Answers (1)

Bryan Oakley
Bryan Oakley

Reputation: 385980

GUI programs are event-based. That means that you don't normally write application code that waits for something since the GUI framework is always waiting for all events. In tkinter this happens when you call mainloop(). Instead, you configure the GUI to respond to specific events. In your case, the event is when the space key has been pressed.

In tkinter you do this with the bind method. You call it on a widget and you specify an event and a function, and tkinter will wait for that event and then call that function.

So, sometime after creating self.type_text you need to add something like following code:

self.type_text.bind("<space>", self.space_detected)

Tkinter will call self.space_detected whenever the user types a space. You then need to define the space_detected method. It will be passed an event, though you won't need the information in this object.

Within that function, you can put any logic you want. For example, this is where you would want to unhighlight the current word and highlight the next.

Due to how tkinter processes events, note that this function will be called before the space is actually inserted into the widget. This is by design, and is actually a very powerful feature of tkinter. For more information, see this answer which explains it in a bit more detail.

Upvotes: 0

Related Questions