-3

Because I've used OOP all frames are initialised and run functions even without populating the set container. This means that if my game is running it will continue to run even when the window is changed. I would like to make it so that if a new frame is shown all functions/methods stop running for other windows. Currently, if the game is started the countdown will continue to go regardless of what's being shown (eg the menu screen).

Here is the class that all the windows inherit from and are sorted:

class TextTypers(tk.Tk):

def __init__(self, *args, **kwargs):  # Runs when our class is called and allows almost anything to be passed

    tk.Tk.__init__(self, *args, **kwargs)  # Initialise Tk
    window = tk.Frame(self)  # Creates the container the windows/frames will populate
    window.pack(fill="both", expand=True)

    self.frames = {}  # Creates a dictionary for the frames

    for F in (MenuScreen, GameScreen, InstructionsScreen):
        frame = F(window, self)  # Adds all frames to dictionary so they can be accessed later
        self.frames[F] = frame
        frame.grid(row=0, column=0, sticky="nswe")

    self.show_frame(MenuScreen)  # Shows the menu screen as this is initialised when the program is run

def show_frame(self, cont):
    frame = self.frames[cont]  # Grabs value of self.frames and puts in in frame
    frame.tkraise()  # Raises frame to the front

Here is the relevant GUI for the game screen:

class GameScreen(tk.Frame):
    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)  # Inherits from main class

        self.label = tk.ttk.Label(self, text="Type the words as quick as you can!",
                                  font=("arial", 35, "bold"), justify="center")
        self.label.grid(row=1, column=3, pady=10, padx=10)

        self.time_label = tk.ttk.Label(self, text="Time left: " + str(timer), font=("arial", 30, "bold"),
                                       justify="center")
        self.time_label.grid(row=3, column=3, columnspan=2, padx=10, pady=10)

        self.word_label = tk.ttk.Label(self, font=("arial", 25, "bold"), justify="center")
        self.word_label.grid(row=5, column=3, padx=10, pady=10)

        self.entry = tk.DoubleVar()
        self.entry.set("")
        self.entry = tk.ttk.Entry(self, textvariable=self.entry, font=("arial", 25, "bold"), state='disabled')
        self.entry.grid(row=6, column=3)

        self.start_button = tk.Button(self, text="Start!", font=("arial", 20, "bold"),
                                      command=self.game, width=10, height=2)
        self.start_button.grid(row=7, column=3, pady=10, padx=10)
        self.entry.bind('<Return>', self.check_word)

        self.home_button = tk.Button(self, text="Home", font=("arial", 20, "bold"),
                                     command=lambda: controller.show_frame(MenuScreen), width=10, height=2)
        self.home_button.grid(row=8, column=3, pady=10, padx=10)

    def game(self):
        global timer
        timer = 60
        self.entry.config(state="enabled")
        self.entry.delete("0", "end")
        self.start_button.config(text="Stop", command=lambda: self.stop())
        if timer == 60:  # Starts the timer and calls the words function
            self.countdown()
        self.words()

    def countdown(self):
        global timer

        if timer > 0:
            timer -= 1
            # Update the time left label
            self.time_label.config(text="Time left: " + str(timer))

            # Run the function again after 1 second
            self.time_label.after(1000, self.countdown)

    def words(self):
        global timer
        global word
        if timer > 0:
            self.entry.focus_set()  # Activate the entry box
            word = random.choice(word_list[0])
            self.word_label.config(text=word)
        if timer == 0:
            self.results(score)

    def check_word(self, event):
        global word, score, wrong

        entered_word = self.entry.get().lower().strip()
        if entered_word == word.lower().strip():
            score += 1
        else:
            wrong += 1
        self.words()
        self.entry.delete('0', 'end')

    def results(self, score):
        global top_score
        self.entry.config(state='disabled')
        if score > top_score:
            top_score = score
        else:
            top_score = top_score
        accuracy = score / (score + wrong) * 100
        self.word_label.config(
            text=f"In one minute you correctly typed {score} words.\n"
                 f"This is a WPM of {score}, your actual WPM is most likely higher.\n "
                 f"You got {score} words correct and {wrong} words wrong. \n"
                 f" That gives you an accuracy of {accuracy:.1f}%!\n"
                 f"Your top score is {top_score}!", font=("arial", 20, "bold"))
        self.start_button = tk.Button(self, text="Play again?", font=("arial", 20, "bold"), command=self.game)
        self.start_button.grid(row=7, column=3, pady=10, padx=10)
        return top_score
Cxnnxr09
  • 11
  • 4
  • Does this answer your question? [how-would-i-make-a-method-which-is-run-every-time-a-frame-is-shown-in-tkinter](https://stackoverflow.com/questions/35029188) – stovfl May 21 '20 at 11:01
  • Nah, I'm more after the code to stop a function from running when a button clicked or have functions stop when changing windows. I need the actual code to make methods stop/cancel. – Cxnnxr09 May 21 '20 at 11:28
  • What I stopping you: ***to stop a function from running***, from inside the callback? – stovfl May 21 '20 at 11:41

1 Answers1

0

You must keep a reference to the call back id in self.countdown():

self.current_callback_id = self.time_label.after(1000, self.countdown)

Then call after_cancel(self.current_callback_id) upon changing frames.

That will probably require a bit of machinery in the changing frame logic.

Reblochon Masque
  • 35,405
  • 10
  • 55
  • 80