tkinter Button click to start thread to prevent GUI from freezing - python

I have a tkinter interface with two buttons to trigger two modes of my application and a label which shows which mode it is in. When a button is clicked, the resulting function that is called takes a while to execute and come back. This results in my mouse cursor spinning and the button being essentially "frozen" until the function finishes its execution before I can click another button.
The behavior that I want is that I click a button, it runs the function asynchronously (new thread) and leaves the buttons clickable again. If another button is clicked, that first thread immediately is killed and the new function/thread starts up asynchronously.
How do I go about achieving this?
def alert_mode(var):
print("Entering Alert Mode")
var.set("Current mode: Alert")
// do stuff that takes a while to return
def capture_mode(var):
print("Entering Capture Mode")
var.set("Current mode: Capture")
// do stuff that takes a while to return
root = tk.Tk()
root.geometry('400x400')
var = StringVar()
var.set("Current mode: Alert")
text = tk.Label(root, text="Current mode: Alert", textvariable=var, fg="blue", font=("Arial", 18))
text.pack(pady=8)
b = tk.Button(root, text="Alert mode", height=10, width=15, font=("Arial",14), command=lambda: alert_mode(var))
b.pack()
b2 = tk.Button(root, text="Capture mode", height=10, width=15, font=("Arial",14), command=lambda: capture_mode(var))
b2.pack()
root.mainloop()
Edit:
Okay, here is the updated code. I tried to figure out how to start and stop a thread but this doesn't quite work the way I want it to. I actually don't want both threads to be running at once. It's either alert mode or capture mode. The point is that I don't want the GUI to freeze up while either mode is running so that the user can switch to the other mode at any time.
def alert_mode(var):
print("Entering Alert Mode")
var.set("Current mode: Alert")
// do stuff that takes a while to return
def capture_mode(var):
print("Entering Capture Mode")
var.set("Current mode: Capture")
// do stuff that takes a while to return
def start_alert_thread(var):
t = threading.Thread(target=alert_mode, args=(var,))
t.start()
t.join()
def start_capture_thread(var):
t2 = threading.Thread(target=alert_mode, args=(var,))
t2.start()
t2.join()
root = tk.Tk()
var = StringVar()
var.set("Current mode: Alert")
text = tk.Label(root, text="Current mode: Alert", textvariable=var, fg="blue", font=("Arial", 18))
text.pack(pady=8)
b = tk.Button(root, text="Alert mode", height=10, width=15, font=("Arial",14), command=lambda: start_alert_thread(var))
b.pack()
b2 = tk.Button(root, text="Capture mode", height=10, width=15, font=("Arial",14), command=lambda: start_capture_thread(var))
b2.pack()
root.mainloop()

Use threading,
import threading
option = 0
def buttonOne():
global option
if option == 2:
"kill statement (not sure what)"
option = 1
else:
option = 1
def buttonTwo():
global option
if option == 1:
"kill statement (not sure what)"
option = 2
else:
option = 2
have the button run a function like this.
def threadButtonOne():
threading.Thread(target=buttonOne).start()

I got way out for such condition:
Condition:
Running selenium webdriver with python and tkinter. When play button is pressed from where handle goes to python program, tkinter GUI window turns into "Not responding" mode, but python program continues.
Note: indent- is just a placeholder, like indentation is important in python
Solution:
import threading, tkinter # and so on
def python_main( ):
input("Enter number of users") #and so on.
def python_sub( ): #and complete program of multiple functions
pass
tkwindow = tk.Tk()
def mid():
th = threading.Thread(target=python_main)
th.start()
def tkinter_func():
button = tk.Button(tkwindow, text= "press me", border=0, command=mid)
button.place(x=4,y=4)
tkwindow.mainloop()
Description: No need for wait. NO auto run program.

Related

how to change tkinter button for online few seconds

def click():
button1.configure(bg="gray")
time.sleep(1)
button1.configure(bg="green")
button1 = Button(win, text="Button",bg="green",activebackground="red")
button1.pack()
I tryied to change button to gray for only second than change back to green. But it won't change to gray
You need to bind that event after the packing
from tkinter import *
import time
def click(event):
if button1["background"] == "green":
time.sleep(3)
button1["background"] = "yellow"
else:
button1["background"] = "green"
root = Tk()
myContainer1 = Frame(root)
myContainer1.pack()
button1 = Button(myContainer1, text="Button", bg="green")
button1.pack()
button1.bind("<Button-1>", click) # binding event here
root.mainloop()
btw, solid resource on the subject, a bit dated but as an educational material - written perfectly - short :D
http://thinkingtkinter.sourceforge.net/
You have to do it this way.
If you use the Time library, the software won't work
or you can use the Threading module for multithreading in Python but This method is a bit complicated
from tkinter import *
def click(event):
def loop(i):
if i==1:
button1.configure(bg="gray")
elif i==2:
button1.configure(bg="red")
return
i=i+1
button1.after (1000, lambda : loop (i))
loop (1)
root = Tk()
myContainer1 = Frame(root)
myContainer1.pack()
button1 = Button(myContainer1, text="Button", bg="red")
button1.pack()
button1.bind("<Button-1>", click) # binding event here
root.mainloop()
First click() has never been executed. Second all updates are handled by tkinter mainloop(), so those changes will be handled after the function click() exits and the last change will be seen only.
You can use .after() instead of sleep() to change the color of the button back to green after one second:
def click():
button1.configure(bg="gray")
# change the background color back to green after 1 second
button1.after(1000, lambda: button1.configure(bg="green"))
button1 = Button(win, text="Button", bg="green", activebackground="red", command=click)
button1.pack()

How to control processes using Tkinter?

I want to use the tkinter to build a GUI to control the python script.
The code looks like,
kansai = Page(kansai_url)
tokyo = Page(tokyo_url)
def loop_main():
with concurrent.futures.ProcessPoolExecutor() as executor:
k = executor.submit(kansai.compare)
t = executor.submit(tokyo.compare)
kansai_lbl['text'] = k.result()
tokyo_lbl['text'] = t.result()
root.after(60000, loop_main)
if __name__ == '__main__':
root = tk.Tk()
# --buttons--
start_btn = tk.Button(root, text='Start', command=loop_main, font='Raleway', bg='#20bebe', fg='white', height=2,
width=10)
start_btn.grid(column=1, row=3)
refresh_btn = tk.Button(root, text='Refresh', font='Raleway', bg='#20bebe', fg='white', height=2, width=10)
refresh_btn.grid(column=2, row=3)
quit_btn = tk.Button(root, text='Quit', command=root.destroy, font='Raleway', bg='#20bebe', fg='white', height=2,
width=10)
quit_btn.grid(column=3, row=3)
# -- instruction --
kansai_name_lbl = tk.Label(root, text='Kansai', font='Raleway')
kansai_name_lbl.grid(column=1, row=0)
tokyo_name_lbl = tk.Label(root, text='Tokyo', font='Raleway')
tokyo_name_lbl.grid(column=3, row=0)
kansai_lbl = tk.Label(root)
kansai_lbl.grid(column=1, row=1)
tokyo_lbl = tk.Label(root)
tokyo_lbl.grid(column=3, row=1)
root.mainloop()
My goal is that, I want to control the running of the script via the start and stop buttons. The script is written as the loop_main running with multiprocessing, takes about 20secs to finish.
My problem is when I click start, the script started but the GUI just went no responding and I can't click the quit button. Only during the interval of running, I can click the buttons. But I want to exit the script via quit button at any time.
How can I fix this?
I had an issue with tkinter gui becoming unresponsive while application was executing a function. For me the solution was "threading":
import tkinter
import time
from threading import Thread
def start():
def something_slow():
global stop
stop = False
while not stop:
print("doing stuff")
time.sleep(1)
print("stoped doing stuff")
executing = Thread(target=something_slow)
executing.start()
def stop():
global stop
stop = True
main_window_of_gui = tkinter.Tk()
button_start = tkinter.Button(main_window_of_gui, text="Start", command=start)
button_start.grid(row=0, column=0)
button_stop = tkinter.Button(main_window_of_gui, text="Stop", command=stop)
button_stop.grid(row=0, column=1)
main_window_of_gui.mainloop()
stop = True

tkinter - how to update button text before command function ends?

I have read numerous questions here but none actually answered my puzzle:
openFolderBtnGenerate = Button(top, text="Generate", command=lambda: compute(csv_file_name, lan, lon, alt))
openFolderBtnGenerate.grid(row=5, column=1)
def compute(csv_file_name, lat_0, lon_0, alt_0):
global openFolderBtnGenerate
openFolderBtnGenerate['text'] = "Please wait..."
# here mathematical computation code that takes 10 seconds to accomplish
...
# end
I intended the button text to change before the computation starts so users understand it might take some time to accomplish.
What really happens is that the button text is changed only after the computation ends.
How can I make the button text change right after the button is clicked, without having to wait these long 10 seconds?
What I think you want to do is just to change text as you press the button then run the function, so a small thing you can do is make another function CODE :
Def function2()
global openFolderBtnGenerate
openFolderBtnGenerate['text'] = "Please wait..."
compute(csv_file_name, lan, lon, alt)
openFolderBtnGenerate = Button(top, text="Generate", command=function2)
openFolderBtnGenerate.grid(row=5, column=1)
def compute(csv_file_name, lat_0, lon_0, alt_0):
# here mathematical computation code that takes 10 seconds to accomplish
...
# end
It works for me and will surely work for you too.
See line "root.update_idletasks()":
import time
import tkinter as tk
root = tk.Tk()
def start_task():
btn_text.set("Please wait for task to finish...")
btn.update_idletasks()
time.sleep(2)
btn_text.set("Press to start a task")
btn_text = tk.StringVar()
btn = tk.Button(root, textvariable=btn_text, command=start_task)
btn_text.set("Press to start a task")
btn.pack()
root.mainloop()
And another sample, without time.sleep() (for UI not to freeze):
import time
import tkinter as tk
root = tk.Tk()
def update_text():
btn_text.set("Press to start a task")
def start_task():
btn_text.set("Please wait for task to finish...")
btn.update_idletasks()
root.after(2000, update_text)
btn_text = tk.StringVar()
btn = tk.Button(root, textvariable=btn_text, command=start_task)
btn_text.set("Press to start a task")
btn.pack()
root.mainloop()

Can I get the text on a button when changed after clicking on it in Python?

Here is an example of what I am trying to convey:
from tkinter import *
def start():
print("Start")
B1.pack_forget()
B2.pack()
def stop():
B2.pack_forget()
B1.pack()
root = Tk()
root.title("TestWin")
B1 = Button(root, text='start', command=start)
B1.pack()
B2 = Button(root, text='stop', command=stop)
root.mainloop()
Now I want to use if logic to get the name of the button currently packed. And it could look something like:
if <button_text_keyword> == 'start' then print('Start') elif <button_text_keyword> == 'stop' then print("Stop").
Can this be done??? Or Do I have to type a long code in order to achieve that???
Please suggest a good method to do what I want or rectify me.
Just to be clear, if the goal is just to print whatever text the button is showing, the simplest is to just have separate command functions, as you have done. It's then explicitly clear which button was there at the time of the click. So by this I mean, just add a print statement in the stop function.
But assuming there is a more nuanced situation where you actually need to determine what is packed in a different part of the program, this is an example based on what #acw1668 suggested in the comments:
from tkinter import *
packed = 'B1'
def start():
global packed
B1.pack_forget()
B2.pack()
packed = 'B2'
def stop():
global packed
B2.pack_forget()
B1.pack()
packed = 'B1'
def check():
if packed == 'B1':
print("Start")
elif packed == 'B2':
print("Stop")
root = Tk()
root.title("TestWin")
B1 = Button(root, text='start', command=start)
B1.pack(side=TOP)
B2 = Button(root, text='stop', command=stop)
B3 = Button(root, text='check', command=check)
B3.pack(side=BOTTOM)
root.mainloop()
The new button ("check") determines what button is packed, based on the flag packed (just to note, if you instead wrap the application in class (see here), you could avoid having to use global).
The above is more than sufficient, but if you actually want to check the button object itself, you can check the packed elements in a Frame with pack_slaves():
from tkinter import *
def start():
B1.pack_forget()
B2.pack()
def stop():
B2.pack_forget()
B1.pack()
def check():
global button_frame
button = button_frame.pack_slaves()[0]
text = (button.cget('text'))
if text == 'start':
print('Start')
elif text == 'stop':
print('Stop')
root = Tk()
root.title("TestWin")
button_frame = Frame(root,)
button_frame.pack(side=TOP)
B1 = Button(button_frame, text='start', command=start)
B1.pack()
B2 = Button(button_frame, text='stop', command=stop)
B3 = Button(root, text='check', command=check)
B3.pack(side=BOTTOM)
root.mainloop()
This second example sounds more similar to the logic you described; i.e. find object corresponding to the pressed button, then get its text using cget, and route that to determine what is printed. Note that this example finds the button widget by using button = button_frame.pack_slaves()[0], so there is an assumption that there will only be clearly one button packed in button_frame.
Another option would be to use bind to program the button's effects, rather than the command parameter. You could then create a function like check in the above example to bind to both buttons, and you could use this technique to get the pressed widget (and then check it's identity/text, determine what to print, etc....).
If you want that on clicking the button your button text will get change then you try #this
from tkinter import *
root = Tk()
root.title("title")
def start():
#for changing text on button
button_name.config(text="new text on button")
B1 = Button(root, text='start', command=start)
B1.pack()

Python Tkinter Progressbar After [duplicate]

Imagine the following simple example:
def doNothing():
sleep(0.5)
barVar.set(10)
sleep(0.5)
barVar.set(20)
sleep(0.5)
barVar.set(30)
mainWindow = Tk()
barVar = DoubleVar()
barVar.set(0)
bar = Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= Button(mainWindow, text='Click', command=doNothing)
button.grid(row=0, column=0)
mainWindow.mainloop()
What I get when I run this, the progressbar is already at 30% when clicking the button, no progress in front of me. Like attached:
What I need: I can see the progress in front of me (not hanging then suddenly 30%)
Update:
I upadted the code according to #Bernhard answer, but still I can not see the progress in front of me. Just a sudden jump of 30% after waiting 1.5 sec
Seocnd Update:
I'm only using sleep here as a simulation for a process that takes time, like connecting over ssh and grabing some info.
Do not use sleep() in tkinter. The entire reason for you problem is sleep() will freeze tkinter until it is done with its count so what you are seeing is a frozen program and when the program is finally released its already set to 30 percent on the next mainloop update.
Instead we need to use Tkinter's built in method called after() as after is specifically for this purpose.
import tkinter as tk
import tkinter.ttk as ttk
mainWindow = tk.Tk()
def update_progress_bar():
x = barVar.get()
if x < 100:
barVar.set(x+10)
mainWindow.after(500, update_progress_bar)
else:
print("Complete")
barVar = tk.DoubleVar()
barVar.set(0)
bar = ttk.Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= tk.Button(mainWindow, text='Click', command=update_progress_bar)
button.grid(row=0, column=0)
mainWindow.mainloop()
If you want the bar to appear to move smoothly you will need to speed up the function call and reduce the addition to the DoubbleVar.
import tkinter as tk
import tkinter.ttk as ttk
mainWindow = tk.Tk()
def update_progress_bar():
x = barVar.get()
if x < 100:
barVar.set(x+0.5)
mainWindow.after(50, update_progress_bar)
else:
print("Complete")
barVar = tk.DoubleVar()
barVar.set(0)
bar = ttk.Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= tk.Button(mainWindow, text='Click', command=update_progress_bar)
button.grid(row=0, column=0)
mainWindow.mainloop()
Because you are calling the function when the buttion is initialized, you need to loose the '(barVar') in the command=(barVar)). This way you bind the function to the button and don't call it when initializing it.
button= Button(mainWindow, text='Click', command=doNothing)
If you need to pass an argument you need to bypass the calling by using lambda:
button= Button(mainWindow, text='Click', command= lambda: doNothing(barVar))
I think I find the solution.
simply add mainWindow.update() after each progress. So the final code would be:
def doNothing():
sleep(0.5)
barVar.set(10)
mainWindow.update()
sleep(0.5)
barVar.set(20)
mainWindow.update()
sleep(0.5)
barVar.set(30)
mainWindow.update()

Categories

Resources