Tkinter UI Becomes Unresponsive While For Loop Is Running - python

I am having a problem where my Tkinter UI becomes completley stuck and non-interactive while a for loop is running. My example code print "Looping" while it is in the loop and there is a "Cancel" button on the UI which is supposed to stop the loop, but since I am unable to click the "Cancel" button the loop can not be stopped. So my question is how can I make my tkinter UI usable while a loop is running. Here is the example code:
from tkinter import*
import time
root = Tk()
i=10
flag = False
def loop():
flag = True
for i in range(100):
if flag == True:
time.sleep(0.5)
print("Looping")
def canc():
flag = False
btn = Button(root, text="Start Loop", command=loop).pack()
cncl = Button(root, text="Cancel", command=canc).pack()
root.mainloop()
I have tried creating a new thread for the loop function but this does not work.
Updated code, the UI is responsive, but nothing happens when cancel is pressed:
from tkinter import*
import threading
import time
root = Tk()
i=10
flag = False
def loop():
flag = True
for i in range(10):
if flag == True:
time.sleep(0.5)
print("Looping")
def run():
threading.Thread(target=loop).start()
def canc():
flag = False
btn = Button(root, text="Start Loop", command=run).pack()
cncl = Button(root, text="Cancel", command=canc).pack()
root.mainloop()

'flag' isnt a global variable so when it is set to False in canc(), the value of the 'flag' local variable in loop() isnt changed and the loop therefore isnt stopped
also root.update() needs to be used to update the GUI
Remedial actions:
from tkinter import*
import threading
import time
root = Tk()
def loop():
global flag
flag = True
for i in range(10):
if flag == True:
root.update()
time.sleep(0.5)
print("Looping")
def canc():
global flag
flag = False
btn = Button(root, text="Start Loop", command=loop).pack()
cncl = Button(root, text="Cancel", command=canc).pack()
root.mainloop()

I found a way around that problem:
I started my time-consuming job inside a thread and I checked if my thread is still running in a while loop, and inside that, I did update my Tkinter root.
here is my code:
def start_axis(input):
print(input)
time.sleep(5)
def axis():
t = threading.Thread(target=start_axis, args=("x"))
t.start()
while t.is_alive():
try:
root.update()
except:
pass
the args part is important so the thread doesn't call the function immediately

Related

Tkinter window Completly freeze when you move it

The window of Tkinter just completly freeze with all the widgets when I move the Tkinter window and that's my problem I tested it with another code and it always does the same thing
Is the problem exclusively with tkinter?
just move your tkinter window from left to right you will see that absolutely all the program freeze it's incredible
Someone said to put main in a separated thread but how ? Like without example I don't even know what It means :(
how do you put the threads outside of mainloop() ? What does it mean ? I putted root.mainloop() before the thread1 = threading.Thread(target= lambda : fct(), daemon=True) thread1.start() and it does nothing
from tkinter import *
from tkinter import ttk
import time
import threading
import win32api
import pyautogui
root = Tk()
root.geometry('800x438')
root.resizable(False,False)
root.configure(bg='gray')
label = Label(root, text='Display content', fg='yellow', bg='black', font=('Arial', 13), width=20)
label.place(relx=0.5,rely=0.3)
firstentryvar = StringVar()
secondentryvar = StringVar()
firstentry = Entry(root, textvariable=firstentryvar , justify=CENTER, font = ('Arial', 12))
secondentry = Entry(root, textvariable=secondentryvar, justify=CENTER, font = ('Arial', 12))
def displaycontent(*args):
firstentry.pack()
secondentry.pack()
label.bind('<Button-1>', hidecontent)
def hidecontent(*args):
firstentry.pack_forget()
secondentry.pack_forget()
label.bind('<Button-1>', displaycontent)
label.bind('<Button-1>', displaycontent)
def function1(*args):
count = 0
bool = False
while count < 10:
for i in firstentry.get():
if bool == False:
count +=1
print(i)
bool = True
else:
bool = False
def function2(*args):
while True:
if win32api.GetKeyState(0x45) < 0:
print('you pressed e')
thread1 = threading.Thread(target = lambda : function1(), daemon=True)
thread1.start()
thread2 = threading.Thread(target = lambda : function2(), daemon=True)
thread2.start()
root.mainloop()
the code may not mean much but it's enough to reproduce my example, well you will notice that if you click on the display label and then move the window without entering anything in the entries the window will bug/freeze why?
I played a bit with your code and it seems that problem is with your while loops.
Even though you used threads correctly, using while loops this way makes your program uses all the resources to loop into it. What I means is as you started program, even before you press label to show entry widgets, your loops just iterated thousand of times if not tens of thousands.
However, simply putting a time sleep, you can easily stop this exponential resource consuming. However, you shouldn't use time.sleep with tkinter if you aren't using inside threads. As we are using loops inside threads, there is no problem.
For example:
def function1(*args):
count = 0
bool = False
while count < 10:
time.sleep(0.1)
for i in firstentry.get():
if bool == False:
count +=1
print(i)
bool = True
else:
bool = False

Using Ping constantly to verify connection and display feedback on python tkinter

I am trying to make a small app with python tkinter.
in which i will need a ping command to constantly check for connection to a certain device example '192.168.1.21' and tells me if the device is connected or not real time.
Nota: i am using ping3
this is my program:
root = Tk()
root.geometry("400x400")
label_1 = Label(root, text = "Not connected")
label_1.pack()
def testing(label):
if ping('192.168.1.21', timeout=0.5) != None: #To test the connection 1 time
label.config(text = "Connected")
else:
label.config(text = "Not Connected")
label.after(500, lambda : testing(label_1))
def popup(): #A popup that does the same thing
top = Toplevel(root)
top.geometry("150x150")
label_2 = Label(top, text = "Not connected")
label_2.pack()
label_2.after(500, lambda : testing(label_2))
top.mainloop()
btn = Button(root, text = "Popup", command=popup)
btn.pack()
testing(label_1)
root.mainloop()
How can i make it not freeze and keep testing while letting the rest of the program run smoothly.
Thank you and sorry if I made mistakes in the code, i am still new to python in generale.
You can achieve this using threading. This allows the tkinter mainloop and the pinging to happen concurrently which means the GUI doesn't freeze.
import threading, time, sys
root = Tk()
root.geometry("400x400")
label_1 = Label(root, text = "Not connected")
label_1.pack()
current_value = None
continue_thread = True
def doPing():
global current_value, continue_thread
while continue_thread:
current_value = ping('192.168.1.21', timeout=0.5)
time.sleep(0.5) # 500ms delay before pinging again
sys.exit() # Stop thread when window is closed
def testing(label):
global current_value
if current_value != None: #To test the connection 1 time
label.config(text = "Connected")
else:
label.config(text = "Not Connected")
label.after(500, lambda : testing(label))
def popup(): #A popup that does the same thing
top = Toplevel(root)
top.geometry("150x150")
label_2 = Label(top, text = "Not connected")
label_2.pack()
label_2.after(500, lambda : testing(label_2))
top.mainloop()
btn = Button(root, text = "Popup", command=popup)
btn.pack()
ping_thread = threading.Thread(target = doPing)
ping_thread.start()
testing(label_1)
root.mainloop()
continue_thread = False # end thread after window is closed
The program now uses a thread, ping_thread, which runs doPing. doPing calls ping, updates current_value and then waits 500ms before calling itself again. Then in the main thread testing updates the label every 500ms using the value of current_value. When the window is closed and root.mainloop has finished continue_thread is set to false so then sys.exit is called, stopping the thread.
There are 2 global variables current_value and continue_thread. The first allows you to access the return value of ping in testing and the second allows the main thread to tell the ping thread to stop when the Tkinter window is closed. Global variables are not good programming practice and I'd advise using classes instead. I've used them here for the sake of simplicity.

Sharing variables between threads in Python tkinter

Is it possible to get the GUI to update variable 'a' to update in real time while 'thread2' increments the value?
import tkinter as tk
from threading import Thread
import time
a = 0 # global variable
def thread1(threadname):
root = tk.Tk()
w = tk.Label(root, text=a)
w.pack()
root.mainloop()
def thread2(threadname):
global a
while True:
a += 1
time.sleep(1)
thread1 = Thread( target=thread1, args=("Thread-1", ) )
thread2 = Thread( target=thread2, args=("Thread-2", ) )
thread1.start()
thread2.start()
If I create a loop and print 'a' I get the correct result.
def thread1(threadname):
global a
while True:
print(a)
# root = tk.Tk()
# w = tk.Label(root, text=a)
# w.pack()
# root.mainloop()
Any help would be appreciated
When you create your label, that's not a "live" connection. That passes the current value of the variable a. You then enter your main loop, and that thread does nothing else until the application exits. You need to send the new value to a function that executes as part of the main thread, and that function will need access to the label.
This works:
import tkinter as tk
from threading import Thread
import time
class GUI(object):
def __init__(self):
self.a = 0
self.w = tk.Label(root, text=self.a)
self.w.pack()
thread2 = Thread( target=self.thread2, args=("Thread-2", ) )
thread2.start()
def thread2(self,threadname):
while True:
self.a += 1
root.after_idle(self.update)
time.sleep(1)
def update(self):
self.w.config(text=self.a)
root = tk.Tk()
gui = GUI()
root.mainloop()
It is possible to make a live connection using textvariable, but then you have to change the type of a to a tkinter.StringVariable. Check here: Update Tkinter Label from variable
Try using a <tkinter.Tk>.after loop like this:
import tkinter as tk
def loop():
global a
a += 1
label.config(text=a)
# run `loop` again in 1000 ms
root.after(1000, loop)
a = 0
root = tk.Tk()
label = tk.Label(root, text=a)
label.pack()
loop() # Start the loop
root.mainloop()
I used a .after script because tkinter and threading don't go together very well. Sometimes tkinter can crash without even giving you an error message if you try to call some tkinter commands from the second thread.

How to stop a program with infinity loop in Python (tkinter)?

I have a problem with stopping the program. When I click exit button, the mainloop stops but program is still running. I am new to it and I have no idea what to do. I know the issue is the thread is still running and I don't know how to stop it.
This is my code:
from tkinter import *
import simulation as s
import graph as g
import numpy as np
from tkinter import filedialog
import threading
def main():
root = Tk()
root.title("Simulator")
switch = True
def get_filename():
return filedialog.askopenfilename(parent=root)
def play():
def run():
while switch:
s.simulation(s.particle, np.inf, s.initial_time_step, get_filename())
thread = threading.Thread(target=run)
thread.start()
def start_simulation():
global switch
switch = True
v.set('Simulation is running!')
play()
def stop_simulation():
global switch
v.set('Simulation is stopped!')
switch = False
def draw_graphs():
g.create_graphs()
start = Button(root, text='Start simulation', command=start_simulation, width=50)
start.pack()
finish = Button(root, text='Stop simulation', command=stop_simulation, width=50)
finish.pack()
graphs = Button(root, text='Graphs', command=draw_graphs, width=50)
graphs.pack()
exit_button = Button(root, text='Exit', command=root.destroy, width=50)
exit_button.pack()
v = StringVar()
statement = Label(root, textvariable=v)
statement.pack()
root.mainloop()
if __name__ == '__main__':
main()
Create a function for the exit button which also stops your thread
def exit():
switch = False
root.destroy()
And later:
exit_button = Button(root, text='Exit', command=exit, width=50)
You can also call the same method whenever the window is closed with the top right X button. Just bind your method to the event:
root.protocol("WM_DELETE_WINDOW", exit)
Ps: You dont need to use global here, because your nested functions have access to the outer functions variables

How to add timer with start/stop GUI in python

I've been googeling all day, tried loads of different ways, but I can't get this code to work. I want a simple timer that run a function when you press "Start", and stops when you press "Stop".
Example:
When you press start, the function will print every second "Hello World", until you press stop.
My code with some comments so you can understand faster:
import sys
from Tkinter import *
from threading import Timer
# a boolean for telling the timer to stop
stop_timer = False
mgui = Tk()
def work(var):
# stop_timers sets to True or False with Start/Stop
stop_timer = var
# work_done will evaluate if timer should start or be canceled
def work_done(var2):
stop_timer = var2
# if stop was pressed t.start() will be ignored
if stop_timer == False:
t.start()
# if stop was pressed timer will stop
if stop_timer == True:
print "Stopped!"
t.cancel()
t = Timer(1, work, [False])
print "Something"
work_done(var)
mgui.geometry('450x450')
mgui.title('Test')
cmd1 = lambda: work(False)
btn = Button(mgui, text="Start", command =cmd1).place(x=50, y=50)
cmd2 = lambda: work(True)
btn2 = Button(mgui, text="Stop", command =cmd2).place(x=100, y=50)
mgui.mainloop()
As you can tell, I'm new to this shizzle!
Thanks, mates!
This is a generic timer, you can also implement one in tkinter using after:
import time
import Tkinter as tk
import threading
class MyTimer(threading.Thread):
def __init__(self, t):
super(MyTimer,self).__init__()
self.txt = t
self.running = True
def run(self):
while self.running:
self.txt['text'] = time.time()
mgui = tk.Tk()
mgui.title('Test')
txt = tk.Label(mgui, text="time")
txt.grid(row=0,columnspan=2)
timer = None
def cmd1():
global timer
timer = MyTimer(txt)
timer.start()
def cmd2():
global timer
if timer:
timer.running = False
timer= None
btn = tk.Button(mgui, text="Start", command =cmd1)
btn.grid(row=1,column=1)
btn2 = tk.Button(mgui, text="Stop", command =cmd2)
btn2.grid(row=1,column=2)
mgui.mainloop()
By editing some in xndrme's post, I finally got it to work. Thank you!
I'll post the code here for possible future googlers.
import sys
from Tkinter import *
from threading import Timer
mgui = Tk()
def cmd2():
global t
if t:
t.cancel()
def looper():
global t
t = Timer(1, looper)
t.start()
print "Hello World!"
mgui.geometry('450x450')
mgui.title('Test')
btn = Button(mgui, text="Start", command =looper).place(x=50, y=50)
btn2 = Button(mgui, text="Stop", command =cmd2).place(x=100, y=50)
mgui.mainloop()

Categories