Tkinter hangs after using `destroy` and `quit` - python

I'm using Tkinter to show a login dialog and then run my main logic.
I intend for the following snippet to close the window (finish the main loop of tk) after clicking the button and just print indefinitely (it is wrapped in while True is in order for the whole script to continue executing, which simulates a real program logic).
But instead, the following snippet hangs the window and fails to close in macOS Ventura with Python 3.10:
from time import sleep
from tkinter import Tk, Button
def quit():
root.quit()
root = Tk()
Button(root, text="Quit", command=quit).pack()
root.mainloop()
while True:
sleep(1)
print("Running a program logic...")
I've tried to run a functional version of this code (which fails the same) and a threaded version of it (which just crashes since an NSWindow must be created on the main thread).
I just really can't wrap my head around it!
EDIT: Fully working example
EDIT 2: Clarify the intention of this code
EDIT 3: Even more minimal code

Try this:
from Tkinter import *
def quit():
global root
root.quit()
root = Tk()
while True:
Button(root, text="Quit", command=quit).pack()
root.mainloop()

Related

Tkinter mainloop() not quitting after closing window

This is NOT a duplicate of Python tkinter mainloop not quitting on closing the window
I have an app that builds on tkinter. I observed at sometimes after I close the window using the X button, the code will not execute past the mainloop() line. This happens completely randomly about 10% of chance. Rest of the time, it works like a charm. I would like to ask if there are any way to force it. As I said, the code blocks on the mainloop() line, thus calling sys.exit() after it does not help.
I am using Python 3.9.8.
This is not 100% reproducible, but here is something that might trigger the problem:
from tkinter import *
root = Tk()
Label(root, 'hi').pack()
mainloop()
print('exited')
My first thought is to use root.mainloop() instead of tkinter.mainloop(). This makes a difference if you are using several windows.
That said, I did see this a long time ago on some old OSes. Never figured out the reason, so I just wrote my own quit function, like this:
import tkinter as tk
def _quit():
root.quit()
root.destroy()
root = tk.Tk()
root.protocol("WM_DELETE_WINDOW", _quit)
tk.Label(root, 'hi').pack()
root.mainloop()
print('exited')

Python Tkinter threading not working as expected on windows 10

I wrote a Tkinter program that works great except that the entire program freezes when pressing a button that calls a long running function. The user has to wait for the function to finish before doing anything else.
I found a video that showed a solution for this and it worked for the guy in the video but not for me.
The following test case shows the problem.
import tkinter as tk
from random import randint
import time
import threading
root = tk.Tk()
root.title("Test Tkinter Threading")
root.geometry("500x400")
def five_seconds():
time.sleep(5)
my_label.config(text="5 Seconds is up!")
def rando():
random_label.config(text=f'Random Number: {randint(1, 100)}')
my_label = tk.Label(root, text="Hello there!")
my_label.pack(pady=20)
# This works but hangs program while executing five_seconds()
#sleep_button = tk.Button(root, text="5 seconds", command=five_seconds)
#sleep_button.pack(pady=20)
# Supposed to let user keep chosing random numbers while five_seconds() is running.
sleep_button = tk.Button(root, text="5 seconds",
command=threading.Thread(target=five_seconds).start())
sleep_button.pack(pady=20)
random_button = tk.Button(root, text="Pick Random Number", command=rando)
random_button.pack(pady=20)
random_label = tk.Label(root, text="")
random_label.pack(pady=20)
root.mainloop()
The guy in the video was able to ckick on the sleep button and keep clicking on the random button as expected. When I run this on windows 10 I get a strange result. If I click on the random button the five_seconds() function executes (even though I never hit the sleep buttton). The random button works and does not seem to hange but the program execution is messed up.
Is there an issue with windows 10 and threading Tkinter?

How to close a running windows OS program using a button?

I'm making a simple GUI using Python 3.7.3 and tkinter to open and close windows applications. I'm not able to find a way to close a running program using an onscreen button. I need the 'close' button to do something else as well, hence the simply using 'x' button (which is next to the minimize and maximize) won't work for my case.
from tkinter import *
import os, subprocess
root = Tk()
root.geometry("300x300")
def OpenCalc():
app1 = os.startfile("C:\Windows\System32\calc.exe")
def CloseCalc():
os.close(app1)
# or
# os.closefile("C:\Windows\System32\calc.exe")
b1=Button(root, text="Open Calc", command=OpenCalc).pack()
b2=Button(root, text="Close Calc", command=CloseCalc).pack()
root.mainloop()

Combining Tkinter mainloop with another event listener

I am trying to build a program that listens for certain key combinations and then shows information to the user in a Tkinter window. To do this, I'm using a keylogger like so (simplified for this example):
from pyHook import HookManager
from pythoncom import PumpMessages
import Tkinter as tk
def on_keyboard_event(event):
label.config(text=event.Key)
root.update()
return True
hm = HookManager()
hm.KeyDown = on_keyboard_event
hm.HookKeyboard()
root = tk.Tk()
label = tk.Label(root, text='Hello world')
label.pack()
PumpMessages()
As expected, the window pops up and shows the user what key they pressed. However, I would like to integrate functionality to show other messages by interacting with the Tkinter window, such as by pressing a button. However, it seems I need Tkinter's mainloop to do this, which I can't figure out how to run alongside PumpMessages(), since it also halts the code similar to mainloop().
I tried running root.mainloop() in a root.after(), and I tried recreating root.mainloop like so:
def mainloop():
root.update()
root.after(50, mainloop)
and then running it right before PumpMessages, but neither of these solutions worked. It also doesn't seem like you can run PumpMessages or root.mainloop in a thread, though I could just not be doing it right. If this is not possible with Tkinter, is there an alternate Python GUI I could use that would make it possible?
You don't need to create a function to use mainloop() so just simply place the mainloop() at the bottom of your code. If you want a delay on it, use root.after(milliseconds, function)
Also, remember to put mainloop() before PumpMessages()
e.g.
def mainloopfunction():
mainloop()
root.after(5000, mainloopfunction)
Hope I could help!

Restart program tkinter

I am wondering on how I can create a restart button that once clicked, can restart the entire script. What I thought was that you destroy the window then un-destroy it but apparently there is no un-destroy function.
I found a way of doing it for a generic python program on this website: https://www.daniweb.com/programming/software-development/code/260268/restart-your-python-program. I wrote an example with a basic tkinter GUI to test it:
import sys
import os
from tkinter import Tk, Label, Button
def restart_program():
"""Restarts the current program.
Note: this function does not return. Any cleanup action (like
saving data) must be done before calling this function."""
python = sys.executable
os.execl(python, python, * sys.argv)
root = Tk()
Label(root, text="Hello World!").pack()
Button(root, text="Restart", command=restart_program).pack()
root.mainloop()
The following solution works as well but is quite harsh, i.e. the entire environment is lost.
# kills the whole application and starts a fresh one
def restart():
root.destroy()
root = Tk()
root.mainloop()
I would Like to Use this Function:-
First of All Import os Module
import os
Then Use this Code:-
# Restarts the Whole Window
def restart():
root.destroy()
os.startfile("main.py")
Or if You want no console behind then Simply Change the extension of the file to .pyw
And Run this Code:-
# Restarts the Whole Window
def restart():
root.destroy()
os.startfile("main.pyw")

Categories