Break ongoing loop started by a tkinter button - python

I want to build a tkinter interface that can start doing some repetitive work when a "start" button is pressed, and break the ongoing loop when the "stop" button is pressed. However I noticed that the tkinter does not process any further job untill the ongoing loop is finished, (e.g. does not respond to the "stop" button click, does not update the textbox or label text as shown in the attached code). It seems to be related to the single thread nature of tkinter from what I read at stackoverflow. Can someone help with some specific code? I am new to tkinter/Python, I could not figure out a real solution although I read many general discussions on board.
import time
import Tkinter as Tk
def _start():
for outer in range(5):
if active_stat:
time.sleep(1) # some code in the real app
else:
break
for inner in range(5):
if active_stat:
#counterstr.set("%02d-%02d" % (outer,inner)) #does not update till the end of loop
textbox.insert(Tk.END, "%02d-%02d\n" % (outer,inner)) #does not show till the end of loop
print "%02d-%02d" % (outer,inner)
time.sleep(1) #some code in the real app
else:
break
def _stop():
active_stat=False
active_stat=True
root = Tk.Tk()
#counterstr=Tk.StringVar()
#Tk.Label(root, textvariable=counterstr).pack(side=Tk.TOP)
textbox=Tk.Text(root)
textbox.pack(side=Tk.TOP)
Tk.Button(root, text='Start', command=_start).pack(side=Tk.LEFT)
Tk.Button(root, text='Stop', command=_stop).pack(side=Tk.LEFT)
Tk.Button(root, text='Quit', command=root.quit).pack(side=Tk.LEFT)
root.mainloop()

What I would do is, is that I would make your active_stat a tkinter variable, and then call its get method when ever you want to check if it:
EDIT: also, add root.update() to your loop. That should fix your problem. The reason for the tkinter variables is because of variable scope problems, which I initially though where your problem. (note: this code is for python 3...)
import time
import tkinter as Tk
def _start():
for outer in range(5):
if active_stat.get():
time.sleep(1) # some code in the real app
else:
active_stat.set(True)
break
for inner in range(5):
if active_stat.get():
#counterstr.set("%02d-%02d" % (outer,inner)) #does not update till the end of loop
textbox.insert(Tk.END, "%02d-%02d\n" % (outer,inner)) #does not show till the end of loop
print ("{}-{}".format(outer,inner))
time.sleep(1) #some code in the real app
else:
active_stat.set(True)
break
root.update()
def _stop():
active_stat.set(False)
root = Tk.Tk()
active_stat = Tk.BooleanVar(root)
active_stat.set(True)
#counterstr=Tk.StringVar()
#Tk.Label(root, textvariable=counterstr).pack(side=Tk.TOP)
textbox=Tk.Text(root)
textbox.pack(side=Tk.TOP)
Tk.Button(root, text='Start', command=_start).pack(side=Tk.LEFT)
Tk.Button(root, text='Stop', command=_stop).pack(side=Tk.LEFT)
Tk.Button(root, text='Quit', command=root.quit).pack(side=Tk.LEFT)
root.mainloop()

Related

Tkinter window not closing until python program ends

I got a behavior that I don't understand in my test program;
I'm using tkinter to display a window that show some radio box, and sent back the value to the python program that will continue to execute.
But I've noticed that after the Tkwindow is asked to close with a destroy() command :
IF the following of my code is asking an input() the window is closing as requested,
BUT if the following of my code continue to execute without any user input, the window will not close until all the code ends.
the code after the maintop is basically some selenium based code that fills an online webpage.
But as I wrote earlier, if I ask an input() (after the maintop here), the window does close, but if I don't ak an input and let the code with selenium fills a webpage, the window will not close until the webpage fills (and it can take some times as it has multiple pages calls.)
def Close():
window.destroy()
window.quit()
def selection():
global bsiInput
bsiInput=str(value.get())
window = Tk()
window.geometry("350x350")
value = StringVar()
label = Label(window, text="Choose : ")
bouton1 = Radiobutton(window, text=" A ", variable=value, value='a',command=selection)
bouton2 = Radiobutton(window, text=" B " , variable=value, value='b',command=selection)
boutonOk = Button(window, text="Valider", command=Close)
label.pack()
bouton1.pack()
bouton2.pack()
boutonOk.pack()
window.mainloop()
Edit: I try both remarks suggested:
So, as I tried what you both suggest, I defines all my code in a MainProgram function.To be clear I just put a print("closed") after the mainloop(), and in my MainProgram() I start it with a print("prog starts"), from there:
def Close():
window.destroy()
window.after(0,MainProgram)
//outputs "closed" and end program as if MainProgram is never called
def Close():
window.destroy()
...
window.mainloop()
window.after(0,MainProgram)
//outputs "closed" and end programs as if MainProgram is never called
and
window.mainloop()
MainProgram()
//outputs "closed""prog starts" but don't close the window until the end of the program

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 commands priority

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.

Tkinter not waiting for user input inside functions

I am trying to make a program that when conditions are met, goes back to the beginning and waits. But instead of waiting for the user to push a button, it continues through the code.
I am using python 3.7.4 and Windows 10.
I assume this problem occurs because tkinter doesn't wait for user input in this situation, and instead continues through the code.
My code:
from tkinter import *
from tkinter.ttk import *
def start():
print("Start")
# Removes all widgets without destroying root
for widget in root.winfo_children():
widget.destroy()
button_1 = Button(root, text="Begin", command=begin).pack()
button_2 = Button(root, text="Do something else", command=something).pack()
# I want the program to wait here for the user to click a button
def begin():
print("\nDoing stuff")
if True:
start()
print("This should not be printed")
def something():
pass
root = Tk()
root.geometry("300x300")
btn1 = Button(root, text = "Start", command = start)
btn1.pack()
root.mainloop()
This outputs:
Start
Doing stuff
Start
This should not be printed
I want this to output:
Start
Doing stuff
Start
And then wait for the user to select a button.
If you want a function to wait for a user action, you need to explicitly tell it to wait.
Tkinter has three functions for that. One is wait_window, which will wait for a window to be destroyed. One is wait_visibility, which will wait for the visibility of a window to change. The third one is wait_variable, which waits for a specific tkinter variable to be set.
While tkinter is waiting, it's able to service other events.
In your case, the solution might look something like this:
var = BooleanVar(value=False)
def do_something():
something()
var.set(True)
button_2 = Button(root, text="Do something else", command=do_something).pack()
print("waiting...")
root.wait_variable(var)
print("done waiting.")
When you modify your code to include the above snippet, you'll notice that "waiting..." will be printed on stdout, and then nothing else will be printed until you click on the "Do something else" button and something returns, allowing the variable to be modified.

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)

Categories

Resources