Python Tkinter commands priority - python

I have faced a strange problem in my program.
from tkinter import *
import time
class Window:
def __init__(self):
self.root = Tk()
self.root.title('Test')
self.root.geometry('400x500')
self.root.resizable(FALSE, FALSE)
self.root.configure(bg ='#1A181B')
def draw_widgets(self):
Button(self.root, text='Start', font='Verdana 17',command = self.start_bot).grid(row=1, column=1)
def run(self):
self.draw_widgets()
self.root.mainloop()
def start_bot(self):
Button(self.root, text='Start', font='Verdana 17', command=self.start_bot).grid(row=2, column=1)
time.sleep(4)
print('a')
win = Window()
win.run()
win.draw_widgets()
As you can see after pressing a button, I want to create another button, then wait for 4 seconds, then print 'a', but it is doing another thing: 1) Waiting for 4 seconds 2) Printing 'a' 3) Creating button.
Please, how I can fix this, I really need your help.

When you use time.sleep() the application suspends processing until the time period is done. This includes updating the GUI changes. To allow the changes to take effect before sleep is started you have to tell the application to do that with update_idletasks(). See example:
def start_bot(self):
Button(self.root, text='Start', font='Verdana 17',
command=self.start_bot).grid(row=2, column=1)
self.root.update_idletasks() # Update GUI changes
time.sleep(4)
print('a')
Have a look at the after() function, which does not suspend processing but schedules something for a later time. This may often be a good function to use instead of sleep.

Related

Exit Python Tkinter app cleanly while also using "wait_variable" function on a button in an "after" loop

I have a problem similar to this post: Exit program within a tkinter class
My variation on the problem involves the wait_variable being used on a button to control "stepping forward" in an app, but also allowing the app to close cleanly.
See my code below:
# To see output unbuffered:
# python -u delete_win_test.py
import tkinter as tk
from tkinter import *
class GUI(Tk):
def __init__(self):
super().__init__()
# Close the app when the window's X is pressed
self.protocol("WM_DELETE_WINDOW", self.closing)
# When this var is set to 1, the move function can continue
self.var = tk.IntVar()
# Close the app if the button is pressed
button = tk.Button(self, text="Exit",
command=self.destroy)
button.place(relx=.5, rely=.5, anchor="c")
# Step forward
self.step_button = tk.Button(self, text="Step",
command=lambda: self.var.set(1))
self.step_button.place(relx=.5, rely=.75, anchor="c")
def move(self):
print("doing stuff") # simulates stuff being done
self.step_button.wait_variable(self.var)
self.after(0, self.move)
def closing(self):
self.destroy()
app = GUI()
app.move()
app.mainloop()
The window shows correctly
"Stepping forward" works because "doing stuff" prints to terminal on button click
Exiting the window by both pressing X or using the "exit" button both work
The problem: the Python app never exits from the terminal and requires a closing of the terminal.
How can I make the Python program exit cleanly so the user does not need to close and re-open a new terminal window?
Related references for animation, etc:
Animation using self.after: moving circle using tkinter
Button wait: Making Tkinter wait untill button is pressed
The original "exit" code: Exit program within a tkinter class
UPDATE (the solution):
(Credit to both response answers below)
# Close the app if the button is pressed
button = tk.Button(self, text="Exit",
- command=self.destroy)
+ command=self.closing)
button.place(relx=.5, rely=.5, anchor="c")
# Step forward
...
def closing(self):
self.destroy()
+ self.var.set("")
+ exit(0)
This allows the native window's "X" to close the window and the Tk button to close the window while still closing the Python app cleanly in the terminal.
Your closing function needs to set the variable to cause the app to stop waiting.
def closing(self):
self.destroy()
self.var.set("")
in the closing function, you need to call exit to exit the program.
def closing(self):
self.destroy() #closes tkinkter window
exit(0) #exits program

Creating "Stop" button using tkinter & classes

I am working on a simulation code, and I had separated by classes the simulation and the interface. I was looking for a stop button to stop my long simulation code, but the interface I have created "Is not responding" until:
the whole simulation stops, or
I get an error, or
I stop it using Spyder "stop command"*
The structure is the following:
class interface(tk.Tk):
def __init__(self):
super().__init__()
self.startInterface()
def startInterface():
self.title('Simulation') # Window's title
self.minsize(450, 180) # Window's size
.
.
.
frm_Mediumdown = tk.Frame(self, bd=7, relief='flat')
frm_Mediumdown.grid(row=3, column=0, padx=0, pady=0)
BTN_stop = tk.Button(frm_Mediumdown, text='Stop', command = self.stop)
BTN_stop.grid(row=1, column=2, sticky=tk.W, padx=4)
BTN_simulate = tk.Button(frm_Mediumdown, text='Simulate',
command = self.Simulate)
BTN_simulate.grid(row=1, column=0, sticky=tk.W, padx=4)
def Simulate(self):
# here this function call another class which start the long simulation code
Simulation.starts(a, b, etc)
def stop(self):
# here it should appear the code option to stop the simulation
class Simulation():
# Long code which do the simulation
.
.
.
if __name__ == '__main__':
print('Start')
app = interface()
app.mainloop()
I have tried to put a global option in the stop and Simulate functions def inside the interface class, but it doesn't work, it has the same problem when I launch the code.
And I've also tried the threading option and the daemon thread, but I didn't get any response.
try doing this instead:
def simulate(self):
simulation = Simulation()
self.after(1000, simulation.starts, a, b, etc)
however does the starts() method run in loop? also try (not suggested) adding root.update() somewhere in the starts() method (root, is whatever is Your root (like interface))

Using Tkinter, Threading and After method

im working on a selenium project using tkinter in python. I want to execute many selenium task at one time(could be up to 100), so I used threading to accomplish this goal, but I came to a problem, due to the fact that I need all individual selenium task to wait a couple seconds between instructions, I used the '.after' method but for some reason my program is still freezing up, I've done a lot of research but I cant seem to find the answer yet. Isn't there a way to use tkinter, threading and some sort of sleeping in python? Do I need to switch to multiprocessing? but I would need all selenium processes to finish within the same 2 minutes and each process takes about a minute.(e.g lets say i start 100, 1 minute processes at 6:00 all task would need to be finished by 6:02)
I created minimal code which mimics my selenium script, so its easier to read, here it is:
from tkinter import *
import tkinter as tk
import time
root = tk.Tk()
root.geometry('700x700')
import threading
class Make:
def __init__(self,num):
self.num = num.get()
Label(root,text='HELLO WORLD WILL PRINT: '+str(self.num)+' times, press go to confirm').pack()
Button(root, text='go', command=lambda: self.starting()).pack()
def starting(self):
for count in range(self.num):
t = threading.Thread(target=gogogo())
t.start()
def gogogo():
tk.Label(root,text='HELLO WORLD').pack()
root.after(2000,print('Please wait 2 seconds'))
tk.Label(root,text='You are inside my world').pack()
Label(root,text='How many times should I print: HELLO WORLD').pack()
num = IntVar()
Entry(root, textvariable=num).pack()
Button(root, text='Start', command=lambda: Make(num)).pack()
root.mainloop()
You main problem is that after() and Thread() similar to command= needs only function's name without () and it will later use () to execute it.
Other problem is that after() doesn't stop code but it only send information to mainloop to execute function after 2000ms and later Python runs at once next line after after(). You have to add Label in fucntion executed by after()
def gogogo():
tk.Label(root, text='HELLO WORLD').pack()
print('Please wait 2 seconds')
root.after(2000, next_message) # function's name without ()
def next_message():
tk.Label(root, text='You are inside my world').pack()
# from tkinter import * # PEP8: `import *` is not preferred
import tkinter as tk
import threading
import time
class Make:
def __init__(self, num):
self.num = num.get()
text = 'HELLO WORLD WILL PRINT: {} times, press go to confirm'.format(self.num)
tk.Label(root, text=text).pack()
tk.Button(root, text='go', command=self.starting).pack()
def starting(self):
for count in range(self.num):
t = threading.Thread(target=gogogo) # function's name without ()
t.start()
def gogogo():
tk.Label(root, text='HELLO WORLD').pack()
print('Please wait 2 seconds')
root.after(2000, next_message) # function's name without ()
def next_message():
tk.Label(root, text='You are inside my world').pack()
# --- main ---
root = tk.Tk()
root.geometry('700x700')
tk.Label(root, text='How many times should I print: HELLO WORLD').pack()
num = tk.IntVar()
tk.Entry(root, textvariable=num).pack()
tk.Button(root, text='Start', command=lambda:Make(num)).pack()
root.mainloop()
EDIT: Because gogogo() runs in separated thread so you can also use time.sleep() because it doesn't block mainloop() in main thread
def gogogo():
tk.Label(root, text='HELLO WORLD').pack()
print('Please wait 2 seconds')
time.sleep(2)
tk.Label(root, text='You are inside my world').pack()

Python Tkinter, destroy toplevel after function

I'm programming some drives with python using Tkinter as GUI. When my machine is running, I'd like to show the user a toplevel window with some information which should close itself after the function completes. This is my minimal example:
from Tkinter import *
import time
def button_1():
window = Toplevel()
window.title("info")
msg = Message(window, text='running...', width=200)
msg.pack()
time.sleep(5.0)
window.destroy()
master = Tk()
frame = Frame(width=500,height=300)
frame.grid()
button_one = Button(frame, text ="Button 1", command = button_1)
button_one.grid(row = 0, column = 0, sticky = W + E)
mainloop()
The main problem is, that the toplevel window just appears after 5 seconds are over. Any suggestions?
Thanks!
time.sleep(5) is launched before the GUI has time to update, that's why the toplevel only appears after the 5 seconds are over. To correct this, you can add window.update_idletasks() before time.sleep(5) to force the update the display.
But, as Bryan Oakley points out in his answer, the GUI is frozen while time.sleep(5) is executed. I guess that your ultimate goal is not to execute time.sleep but some time consuming operation. So, if you do not want to freeze the GUI but do not know how long the execution will take, you can execute your function in a separated thread and check regularly whether it is finished using after:
import Tkinter as tk
import time
import multiprocessing
def function():
time.sleep(5)
def button_1():
window = tk.Toplevel(master)
window.title("info")
msg = tk.Message(window, text='running...', width=200)
msg.pack()
thread = multiprocessing.Process(target=function)
thread.start()
window.after(1000, check_if_running, thread, window)
def check_if_running(thread, window):
"""Check every second if the function is finished."""
if thread.is_alive():
window.after(1000, check_if_running, thread, window)
else:
window.destroy()
master = tk.Tk()
frame = tk.Frame(width=500,height=300)
frame.grid()
button_one = tk.Button(frame, text ="Launch", command=button_1)
button_one.grid(row = 0, column = 0, sticky = "we")
master.mainloop()
A general rule of thumb is that you should never call sleep in the thread that the GUI is running in. The reason is that sleep does exactly what it says, it puts the whole program to sleep. That means that it is unable to refresh the window or react to any events.
If you want to do something after a period of time, the correct way to do that is with after. For example, this will destroy the window after five seconds:
window.after(5000, window.destroy)

python running task in the background while allowing tkinter to be active

When my program executes the python GUI freezes. Here is my main code. Can I get some help in doing threading? So the execution happens in the background and I can still be able to use the "x" button in the GUI if I want to end the execution? Currently I just ask the user to close the cmd to end the program.
if __name__ == "__main__":
root = Tk()
root.title('Log')
root.geometry("400x220")
font1=('times', 15)
font2=('times', 10)
#Label inside root
Label(root, relief=GROOVE, font=font2, text="level").pack()
variable = StringVar(root)
variable.set("INFO") # default value
w = OptionMenu(root, variable, "CRITICAL", "DEBUG")
w.pack()
Button(root, font=font1, background= "yellow", text='START',command=main).pack()
Label(root, text="To end just close the CMD window").pack()
root.mainloop()
UPDATE: Turns out the Button callback was autorunning launch because the function object wasn't being set as the callback, the called function itself was. The fix is to replace the callback lambda: spawnthread(fcn) so that a function object is set as the callback instead. The answer has been updated to reflect this. Sorry for missing that.
The GUI mainloop will freeze when you try to run some other function, and has no way to restart itself (because it's frozen.)
Let's say the command you'd like to run alongside the GUI mainloop is myfunction.
Imports:
import time
import threading
import Queue
You need to set up a ThreadedClient class:
class ThreadedClient(threading.Thread):
def __init__(self, queue, fcn):
threading.Thread.__init__(self)
self.queue = queue
self.fcn = fcn
def run(self)
time.sleep(1)
self.queue.put(self.fcn())
def spawnthread(fcn):
thread = ThreadedClient(queue, fcn)
thread.start()
periodiccall(thread)
def periodiccall(thread):
if(thread.is_alive()):
root.After(100, lambda: periodiccall(thread))
You then want the widget calling the function to instead call a spawnthread function:
queue = Queue.Queue()
Button(root, text='START',command=lambda: spawnthread(myfunction)).pack() #<---- HERE
N.B. I'm adapting this from a multithreaded tkinter GUI I have; I have all my frames wrapped up in classes so this might have some bugs since I've had to tweak it a bit.

Categories

Resources