Why does the destruction of two different windows of mainloop on tkinter freeze the program?

advertisements

I'm doing a countdown (like the TV show) project for school, which involves a small period of time in which the user is entering a response in a tkinter Text() entry (under a time constraint). Thanks to a previous answer on this site, I knew how to make an on-screen tkinter timer, and I used a very basic threading device to put that on at the same time as the user Text() entry window.

It works fine if I don't bother about closing the clock when the user closes the Text() entry window early, but it feels silly when it continues to tick after the user is done with it. So I also inserted some code ("WM_DELETE_WINDOW", etc) that would allow the user to close the entry window and submit their answer early, which closes the timer at the same time. After this point, this made the program freeze up and I'm not sure how to fix it. Here's a simplified version of the code I used (the problem is exactly the same):

import threading
from tkinter import *

def backgroundWindow():
    global root
    root = Tk()
    root.title("Happening at the same time")
    root.mainloop()

threadWindow = threading.Thread(target=backgroundWindow)
threadWindow.start()

print() # The threading did NOT work without this print statement (?)

def destroyer():
    global root
    newRoot.destroy()
    root.destroy()

newRoot = Tk()
newRoot.title("Simultaneous Window")
newRoot.protocol("WM_DELETE_WINDOW", destroyer)

newRoot.mainloop()

# program freezes up here (but does not finish)

print("Now the program continues")  # does NOT get printed

I am also confused about why the second window failed to appear without the seemingly-random print statement in the middle. I tried googling all of this but had no success; this is a pretty niche problem.

EDIT (I CONFUSED MYSELF):

I know someone has already answered this question and I it works great - both the windows closed automatically when closing one of them - but I couldn't get it to work with my countdown program because my knowledge of classes is hazy, and I used a class with one of the windows (it was the only way I managed to get the grid method to work).

The principle is just the same, so I wrongly assumed I could figure it out, but something about running a class that involves another class had me stumped. The contents of the two windows got jumbled up (e.g. the bg colour for one window was unintentionally applied to the other), and when I closed either of the windows, the other one was left open anyway. Does anyone have any tips for running a class and a non-class, with a class? Here is the code without the threading thingy:

def conundrumClock():
    global threading, playerAnswer, playerNumber, tkinter
def clock():
    global threading, rootA
    def countdown(time): # (I copied this from a previous answer by Bryan Oakley)
        global threading
        if time == -1:
            rootA.destroy()
        else:
            if time == 0:
                label.configure(text="TIME UP!")
            else:
                label.configure(text="Time remaining: %d seconds" % time)

            rootA.after(1000, countdown, time-1)

    rootA = Tk()
    rootA.title("COUNTDOWN CLOCK")
    label = Label(rootA, width=30)
    label.pack(padx=20, pady=20)
    countdown(15)
    rootA.mainloop() # I am aiming to get rid of this

    class AnswerEntry(tk.Tk):

        def __init__(self):
            tk.Tk.__init__(self)

            self.anagram = Label(self, text="Your anagram solution", bg="light cyan", font=(None, 15), width=18, height=2, anchor=E)
            self.numerics = Label(self, text="Player number (1 or 2)", bg="light cyan", font=(None, 14), width=19, height=2, padx=4, anchor=E)
            self.anagram.grid(row=1, sticky=E, pady=1)
            self.numerics.grid(row=2, padx=(7,0), pady=5)

            self.solution = tk.Text(self, height=2, width=17,font=(None,15))
            self.number = tk.Text(self, height=2, width=17, font=(None, 15))
            self.solution.grid(row=1, column=1)
            self.number.grid(row=2, column=1) 

            self.button1 = tk.Button(self, text=" SUBMIT YOUR NAME ", bg="cyan", fg="black", width=20, height=1, font=(None, 11), anchor=W, command=self.answer)

            self.button2 = tk.Button(self, text="SUBMIT PLAYER NUMBER ", bg="cyan", fg="black", font=(None, 11), padx=3, command=self.the_number)
            self.button1.grid(row=100, column=1, sticky=E)
            self.button2.grid(row=100, column=0, sticky=W)

        def answer(self):
            global playerAnswer
            playerAnswer = self.solution.get('1.0', END)
        def the_number(self):
            global playerNumber
            playerNumber = self.number.get('1.0', END)

root = AnswerEntry()
root.title("REMEMBER TO PRESS THE BUTTONS!")
root.geometry("500x150")
root["bg"] = "aquamarine"
center(root) # (this is a function I got to center the window from a previous answer)

The first window brings up a timer (that counts down) and the second window brings up a box where you enter your solution to an anagram, and your player number. The buttons underneath allow the player to 'submit' both of these things (I used the the 'self.(text_entry_information).get' thingy on the button command procedure). I've left out any attempt to make these windows run simultaneously, because I did it wrong and it would confuse the question.

Again, any help would be greatly appreciated!


Try only import tkinter as tk more often since it's better for the namespace. This answer is for addressing your EDIT.

import tkinter as tk

def clock(root):
    global rootA
    def countdown(time):
        if time == -1:
            destroyer() #invokes the destroy function
        else:
            if time == 0:
                label.configure(text="TIME UP!")
            else:
                label.configure(text="Time remaining: %d seconds" % time)

            rootA.after(1000, countdown, time-1)

    rootA = tk.Toplevel(root) #Made it a tk.Toplevel instead of a new root
    rootA.protocol("WM_DELETE_WINDOW", destroyer) #set the window close protocol
    rootA.title("COUNTDOWN CLOCK")
    label = tk.Label(rootA, width=30)
    label.pack(padx=20, pady=20)
    countdown(15)

class AnswerEntry(tk.Tk):

    def __init__(self):
        super().__init__() #I changed tk.Tk.__init__(self) to this
        self.title("REMEMBER TO PRESS THE BUTTONS!") #I placed them here since they are the same thing
        self.geometry("500x150")
        self["bg"] = "aquamarine"
        self.protocol("WM_DELETE_WINDOW", destroyer)
        self.anagram = tk.Label(self, text="Your anagram solution", bg="light cyan", font=(None, 15), width=18, height=2, anchor=tk.E)
        self.numerics = tk.Label(self, text="Player number (1 or 2)", bg="light cyan", font=(None, 14), width=19, height=2, padx=4, anchor=tk.E)
        self.anagram.grid(row=1, sticky=tk.E, pady=1)#Changed all your 'W' and 'E'... to 'tk.W' and 'tk.E'
        self.numerics.grid(row=2, padx=(7,0), pady=5)

        self.solution = tk.Text(self, height=2, width=17,font=(None,15))
        self.number = tk.Text(self, height=2, width=17, font=(None, 15))
        self.solution.grid(row=1, column=1)
        self.number.grid(row=2, column=1) 

        self.button1 = tk.Button(self, text=" SUBMIT YOUR NAME ", bg="cyan", fg="black", width=20, height=1, font=(None, 11), anchor=tk.W, command=self.answer)

        self.button2 = tk.Button(self, text="SUBMIT PLAYER NUMBER ", bg="cyan", fg="black", font=(None, 11), padx=3, command=self.the_number)
        self.button1.grid(row=100, column=1, sticky=tk.E)
        self.button2.grid(row=100, column=0, sticky=tk.W)

    def answer(self):
        global playerAnswer
        playerAnswer = self.solution.get('1.0', tk.END)
    def the_number(self):
        global playerNumber
        playerNumber = self.number.get('1.0', tk.END)

def destroyer():
    rootA.destroy() #When the window closes
    root.destroy()

if __name__ == '__main__':
    root = AnswerEntry()
    clock(root)
    root.mainloop()

please write a new question next time so more people could get to it. Since your EDIT was a completely different question.