When I create a tkinter window instance using a thread, even though the window is destroyed after execution, and the thread is joined; I can't make another tkinter window later on in the program flow. Why?
def on_button_click(root): //destroys window on button click
root.destroy()
def init(): //thread calls this
root=Tk()
b = Button(root, text="OK", command=lambda:on_button_click(root))
b.pack()
root.mainloop()
t = Thread(target=init)
t.start()
t.join()
root=Tk() //program flow halts here with no window being displayed
root.mainloop()
From what I can gather using my Google-foo, the problem is that the Tk event loop (which is created during your call to root.mainloop()) is single-threaded and you can only have one instance of it at a time. So that's probably why its getting stuck at that location. Your thread is properly setting up the Tk subsystem but the program fails when you try to create a second Tk subsystem to run at the same time. Your call to root.destroy() is only destroying the windows that you created and not the entire Tk subsystem.
It's been a while since I used Tk but I'd suggest calling root.mainloop() once when you first start your program and then leave your functions to instantiate Tk windows only, not the entire Tk subsystem.
Related
I am learning about Tkinter and was wondering if it would cause errors if I did the following:
import tkinter as tk #import modules
from tkinter import ttk
parent=tk.Tk() #create first instance
card1="k spades"
card2="k diamonds"
comboform=ttk.Combobox(parent,textvariable='form',values=[card1,card2,"both","neither"])#create combobox input form
comboform.grid(row=0,column=0)#added to grid
parent.geometry("200x200")
parent.mainloop()#displays tkinter window
#window exited
parent=tk.Tk()#new instance created
label=tk.Label(parent,text="hi")#label produced
label.pack()#added to window
parent.mainloop()
If I click the exit cross is that the same as parent.destroy(); is that good practice? I know you're only supposed to run mainloop() once and have one Tk() instance but if it's destroyed is it going to cause problems? It's not like I'm creating a class the produces a Tk() instance, where there's a risk of multiple instances existing at once.
I am hoping to, eventually, have an application running in the IDLE and then have a tkinter window appear, presenting an input widget of some kind. After the user gives their input, the window would close and the user would continue in the main window. But could I then do it again, opening new windows (like the above code) on the provision that the instance of Tk() is destroyed each time?
If you've destroyed the root window and then create a new one, that's perfectly fine.
The problem with creating multiple instances of Tk is that most people don't understand what that actually does. Having multiple instances of Tk is fine as long as you realize that they operate in completely memory spaces and widgets and bindings in one can't interact with widgets and bindings in the other.
All of that being said, the best practice is to create a single root window at the start of the program, and it stays alive until the program exits. If you need additional windows, the best practice is to create instances of Toplevel.
So I've been having problems with Tkinter for Python 3 on MacOS 10.15.5. Here's the reproducible code for my system:
import tkinter as tk
import tkinter.messagebox as msgbox
def quit():
root.destroy()
root = tk.Tk()
lbl = tk.Label(root,text="This is a label.")
ent = tk.Entry(root)
btn = tk.Button(root,text="Quit", command=quit)
lbl.pack()
ent.pack()
btn.pack()
root.mainloop()
for i in range(800000):
print(i)
After I press "quit", the for loop runs in the background, but the Tkinter window freezes (with the spinning wheel), until the for loop concludes. I'm trying to create a program that takes user input at the beginning and then runs code in the background, so you can see why I'd rather not have the GUI frozen the whole time.
I'm running this code from the terminal, but I get the same issue using IDLE. I've tried uninstalling and reinstalling several different versions of python (from the python website, since apparently that's the way to use Tk properly), and this problem persists.
Weirdly enough, I've seen that adding an input statement, rather than any other code, immediately after the mainloop, will cause the window to close, but I can't figure out why, and it wouldn't really work with my broader program.
I have made a GUI using Tkinter for my Python script for a Voice Assistant. It is working pretty well. But I want to add an animation window to display an animation that I have created using After Effects as an intro to the app. I want it to open without the default close(x), maximize and minimize buttons. The window should stay till the animation is completed, and then it would disappear. The main window would then open normally to launch the GUI. To disable the close, maximize and minimize buttons I have used the root.overrideredirect(True) method. But I am not being able to simultaneously open the two windows one after the other as mentioned. I would be highly obliged if somebody would help me out with this! I have tested it with a code on a simple GUI. I am providing the code below for help with the problem!
from tkinter import *
import time
root = Tk()
root.geometry('500x300')
root.overrideredirect(True) # To disable the default Window decoration
time.sleep(5) # Assuming that the animation runs for 5 seconds
root.destroy() # This window destroys after being on screen for 5 seconds
root.mainloop()
root2 = Tk() # After the previous window is destroyed, this window opens up. Assume that this is the main window
root2.geometry('500x300')
root.mainloop()
Please help me out!
The thing you call "animation window" is actually called "splash". There is a way to do what you want. You need to create a root window for the app (a Tk instance), and then you should hide (root.withdraw()) it. Now create a Toplevel for the splash, wait for 5 seconds, destroy it, and show (root.deiconify()) the Tk window again.
Note: time.sleep(5) should never be used with Tkinter, use root.after(ms, func) instead.
from tkinter import *
def show_splash():
splash = Toplevel()
splash.geometry('500x300')
splash.overrideredirect(True) # To disable the default Window decoration
splash.after(5000, splash.destroy) # This window destroys after being on screen for 5 seconds
splash.wait_window()
root = Tk()
root.withdraw()
show_splash()
root.deiconify()
root.mainloop()
PS: Procedural programming is not a good idea for pretty complex Tkinter-based apps. You should consider using OOP instead.
PPS: Take a look at this and this answers.
I have been trying to use tkinter to make a gui to select some excel files and sheets from those files.
I have a lot of experience in python, but I'd probably say a novice at tkinter.
The code I have written to select the files is shown below (typos are likely because I cannot access the internet on the machine these files are on, so I am typing it up here).
My question is about mainloop(), the update functions, and after(). I had mainloop() at the end of my code, but my program wouldn't terminate (i.e. the terminal would remain) after it did what it does. So I removed the mainloop() and now it functions perfectly without any mainloop(), update(), or after() calls.
I don't really understand this and would really like to. I get that mainloop() stops the code from progressing until the root closes, but I thought nothing would show up without mainloop(), and this code does wait for the user to close the windows before continuing.
I was also wondering if I do not have mainloop (or the like), the code still closes fine whether or not I have root.destroy() at the end of the App class (commented in the code). I don't get that either.
This information would help me make better and correct code in the future and hopefully improve this one. FYI, I have searched for the answer to this everywhere and could not find one, at least one I could understand. Thanks a bunch.
This is as minimal as I can think of for this code.
Code (Edited from original post):
import tkinter as tk
from tkinter import ttk
class App:
def __init__(self, parent):
self.root = parent
self.root.withdraw()
tl = tk.Toplevel(parent)
b = ttk.Button(tl, text="Test widget")
b.grid()
tl.wait_window()
# This line does not change how the code functions at all
#self.root.destroy()
def run(self):
# Whether or not this is a function in the class or called globally doesn't matter.
self.root.mainloop()
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
# This is the mainloop() call. If I include it, the program does not close after
# running the code in App.__init__. Without it, it runs perfectly.
# app.run()
mainloop enters an event listening loop for your tk.Tk() application.
You should create only one object with Tk() in every tkinter application.
Now this loop will "globally" listen for GUI events and will call your registered callbacks (event handlers). Code before the mainloop call will run but nothing will be shown or updated in the screen until you call it. No event (user input, mouse movement, keyboard and others) will be queued and responded to before that. But the widgets are created and configured, just not shown yet. If you call root.update() without ever entering the mainloop you will eventually see the window flash and disappear because everything is running, and you force window refresh, but then suddenly the program ends.
So configure your GUI and in the end always run root.mainloop()
Now when you call wait_window, you are creating a local event loop (and blocking the mainloop if it was running). For a perfect explanation of wait_window by Brian see here
What happened to you is that your app is happily running with your local event loop, but without mainloop. That would be a strange thing to do. Even if you are making as Brian explains, a modal window, you want to "return" to the mainloop after the window closes.
As for the after method it just registers a callback function to run after your choosen time, or after the mainloop becames idle, with after_idle (nothing more to refresh, no events in the queue..., also think as soon as possible). And if you want to re-run that function again in the same way it should re-register with after or after_idle before returning.
So always use your mainloop() ending call - or should I say event loop starting call :)
Edit:
Also, don't do things in your code that are slow, or that may block the loops, such as calling sleep or any other blocking function. That will freeze the GUI, until the block ends.
I am trying to build an application with a main window, and a background thread that checks a certain condition in a loop, and when the condition is met, brings the main window to the front.
To bring the window to the front i use the lift() method, but it doesn't work.
So I did the following experiment from the python command line:
from Tkinter import *
root = Tk()
A tk window appears. Minimize the window and then:
root.lift()
Nothing happens. I also tried start the window's mainloop before lifting:
import thread
# start mainloop in separate thread so we can continue typing
thread.start_new_thread(root.mainloop, ())
root.lift()
Again, nothing happens. In my actual code it is even worse - once I call lift(), the window is stuck and stops responding.
What am I doing wrong?
(I'm using Python 2.7.2 on Windows 7.)
You cannot run the mainloop in a thread different from the one where you create the widgets. In fact, you can't interact with widgets at all from another thread.
To revert the effects of minimizing the window you need to use the deiconify method.