While Loop Locks Application - python

I have been banging my head for a while now on a application I am working on. After many hours trying to debug an issue where the interface locks up and nothing else can take place I figured out it was the dreaded While loop. See this example below and run it. When you start the while loop by clicking on the button you cannot do anything else on the screen. In this is case it is just a simple alert button that needs pressing.
from Tkinter import *
import tkMessageBox
root = Tk()
root.geometry("450x250+300+300")
root.title("Raspberry PI Test")
def myloop():
count = 0
while (count < 500):
print 'The count is:', count
count = count + 1
print "Good bye!"
def mymessage():
tkMessageBox.showinfo(title="Alert", message="Hello World!")
buttonLoop = Button(root, text="Start Loop", command=myloop)
buttonLoop.place(x=5, y=15)
buttonMessage = Button(root, text="Start Loop", command=mymessage)
buttonMessage.place(x=85, y=15)
root.mainloop()
How can I have a loop that needs to run until a count is completed and still be able to do other tasks in my application? I should also note that I have tried this same thing using a Thread and it doesn't matter. The UI is still waiting for the While loop to end before you can do anything.

now that I understand what you want better (a stopwatch) I would recommend the root.after command
from Tkinter import *
import tkMessageBox
import threading
import time
root = Tk()
root.geometry("450x250+300+300")
root.title("Raspberry PI Test")
print dir(root)
count = 0
def start_counter():
global count
count = 500
root.after(1,update_counter)
def update_counter():
global count
count -= 1
if count < 0:
count_complete()
else:
root.after(1,update_counter)
def count_complete():
print "DONE COUNTING!! ... I am now back in the main thread"
def mymessage():
tkMessageBox.showinfo(title="Alert", message="Hello World!")
buttonLoop = Button(root, text="Start Loop", command=myloop)
buttonLoop.place(x=5, y=15)
buttonMessage = Button(root, text="Start Loop", command=mymessage)
buttonMessage.place(x=85, y=15)
root.mainloop()
(original answer below)
use a thread
from Tkinter import *
import tkMessageBox
import threading
import time
root = Tk()
root.geometry("450x250+300+300")
root.title("Raspberry PI Test")
print dir(root)
def myloop():
def run():
count = 0
while (count < 500) and root.wm_state():
print 'The count is:', count
count = count + 1
time.sleep(1)
root.after(1,count_complete)
thread = threading.Thread(target=run)
thread.start()
def count_complete():
print "DONE COUNTING!! ... I am now back in the main thread"
def mymessage():
tkMessageBox.showinfo(title="Alert", message="Hello World!")
buttonLoop = Button(root, text="Start Loop", command=myloop)
buttonLoop.place(x=5, y=15)
buttonMessage = Button(root, text="Start Loop", command=mymessage)
buttonMessage.place(x=85, y=15)
root.mainloop()
note that when you show the info box that will block at the windows api level so the thread counting will wait till that closes ... to get around that you can just replace threading with multiprocessing I think

I don't really know much about TKinter, but from my reading it's clear that you need to use a some TKinter method in your while loop in order to update your text box. TKinter runs on an event loop so you have to send a signal from your code to re-enter TKinter's execution.
You've done a great job discovering that your while loop is blocking the execution of your UI's updates. So instead of threading you need to just pause your counting's execution and let TKinter update the UI.
This tutorial provides an excellent example. The key is on line 24 where he calls root.update which I believe breaks from your program to let TKinter do it's thing.

Here is the final code just to prove that the thread works. The count is displaying on the screen at the same time as it is happening. Thanks again Joran!
from Tkinter import *
import tkMessageBox
import threading
import time
root = Tk()
root.geometry("450x250+300+300")
root.title("Raspberry PI Test")
showResults = StringVar()
showResults.set('0')
print dir(root)
def myloop():
def run():
count = 0
while (count < 1000) and root.wm_state():
print 'The count is:', count
showResults.set(count)
count = count + 1
#time.sleep(1)
root.after(1,count_complete)
thread = threading.Thread(target=run)
thread.start()
def count_complete():
print "DONE COUNTING!! ... I am now back in the main thread"
def mymessage():
tkMessageBox.showinfo(title="Alert", message="Hello World!")
buttonLoop = Button(root, text="Start Loop", command=myloop)
buttonLoop.place(x=5, y=15)
buttonMessage = Button(root, text="Message", command=mymessage)
buttonMessage.place(x=85, y=15)
l2 = Label(root, width=15, height=4, font=("Helvetica", 16), textvariable=showResults, background="black", fg="green")
l2.place(x=15, y=65)
root.mainloop()

Related

Label not updating in tkinter

I have asked a very similar question I believe 2 days ago and it got closed as a mod told me to look at other similar questions but none of the solutions worked. Does anyone have any idea how to fix the error. Here is the code at the moment all it does is print and display the first number in the sequence:
import tkinter as tk
#import time
window = tk.Tk()
window.title("Hello wold")
window.geometry("300x300")
timer = int(input("time in seconds "))
def update():
global timer
timer -= 1
print(timer)
hello = tk.Label(window, textvariable = timer)
hello.pack()
for i in range(timer):
window.after(1000, update)
tk.mainloop()
There are few issues in your code:
it is not recommended to use console input() in a GUI application
the for loop will be blocked by tk.mainloop() until the root window is closed. However the next iteration will raise exception since the root window is destroyed. Actually the for loop is not necessary.
Below is a modified example:
import tkinter as tk
window = tk.Tk()
window.title("Hello World")
window.geometry("300x300")
# it is not recommended to use console input() in a GUI app
#timer = int(input("time in seconds: "))
timer = 10 # use sample input value
def update(timer=timer):
hello['text'] = timer
if timer > 0:
# schedule to run update() again one second later
hello.after(1000, update, timer-1)
# create label once and update its text inside update()
hello = tk.Label(window)
hello.pack()
update() # start the after loop
# should run mainloop() only once
window.mainloop()
import tkinter as tk
#import time
window = tk.Tk()
window.title("Hello wold")
window.geometry("300x300")
timer = int(input("time in seconds "))
hello = tk.Label(window, text= timer)
hello.pack()
def update():
global timer
timer -= 1
print(timer)
hello.config(text=timer)
for i in range(timer):
window.after(1000, update)
tk.mainloop()
something like this should work
Try this:
import tkinter as tk
import time
window = tk.Tk()
window.title("Countdown Timer")
window.geometry("300x300")
timer = int(input("time in seconds "))
#var = tk.StringVar()
def update(cnt):
if cnt > 0:
window.after(1000, update, cnt-1)
print(cnt)
hello['text'] = cnt
hello = tk.Label(window, text=cnt)
#hello['text'] = timer
hello.pack()
update(timer)
tk.mainloop()

Tkinter get status message from called function displayed in real time

I try to show a status from a called function in real time. However, the messages appears in the GUI all at ones after function is done. What can I do?
Thanks for your help!
from tkinter import *
import time
def sleep():
msgbox.insert(INSERT,"go sleep...\n")
time.sleep(2)
msgbox.insert(INSERT,"... need another 2 sec \n")
time.sleep(2)
msgbox.insert(INSERT,"... that was good\n")
return
root = Tk()
root.minsize(600,400)
button= Button(text="Get sleep", command=sleep)
button.place(x=250, y=100,height=50, width=100)
msgbox =Text(root, height=10, width=60)
msgbox.place(x=20, y=200)
mainloop()
You can use the Tk.update() function to update the window without having to wait for the function to finish:
from tkinter import *
import time
def sleep():
msgbox.insert(INSERT,"go sleep...\n")
root.update()
time.sleep(2)
msgbox.insert(INSERT,"... need another 2 sec \n")
root.update()
time.sleep(2)
msgbox.insert(INSERT,"... that was good\n")
root = Tk()
root.minsize(600,400)
button= Button(text="Get sleep", command=sleep)
button.place(x=250, y=100,height=50, width=100)
msgbox =Text(root, height=10, width=60)
msgbox.place(x=20, y=200)
mainloop()
(also the return is unnecessary, you use it if you want to end a function partway through or return a value from the function).
Let me know if you have any problems :).

Running a thread with Tkinter object

When I push the button the scan_open_ports start working until the line ip_list.curselection() where it stops, this line blocks the running of the function...
I wanted to know why and how to fix that?
Thanks
def scan_open_ports():
#long runtime function
print "Asdasd\"
ip_list.curselection()
def thr_open_ports():
threading.Thread(target=scan_open_ports).start()
ip_list = Listbox()
scan_ports = Button(window, text="Scan Open Ports", command= thr_open_ports, height = 10, width = 20)
I have shamelessly stolen some code from this answer, just as a basis for some tkinter code: How to align label, entry in tkinter
The following code adapts this to have a Queue and a Thread which only runs after a button press.
The Thread communicates back to the mainloop through the Queue which is polled by calls to root.after()
from tkinter import *
from threading import Thread
from queue import Queue
from time import sleep
from random import randint
root = Tk()
root.geometry("583x591+468+158")
root.title("NOKIA _ANSI Performance")
root.configure(borderwidth="1")
root.configure(relief="sunken")
root.configure(background="#dbd8d7")
root.configure(cursor="arrow")
root.configure(highlightbackground="#d9d9d9")
root.configure(highlightcolor="black")
Label3 = Label(root)
Label3.configure(text='''Device IP Address :''')
Label3.pack()
Label5 = Label(root)
Label5.configure(text='''Username :''')
Label5.pack()
Entry5 = Entry(root)
Entry5.pack()
th = None
q = Queue()
def run_me(q):
sleep(5)
q.put(randint(1, 99))
def check_queue():
if not q.empty():
item = q.get()
Label5.configure(text=str(item))
root.after(200, check_queue)
def do_thread():
global th
th = Thread(target=run_me, args=(q,))
th.start()
Button1 = Button(root)
Button1.configure(pady="0")
Button1.configure(text='''NEXT''')
Button1.configure(command=do_thread)
Button1.pack()
root.after(200, check_queue)
mainloop()
The mainloop() is not blocked either by the Thread nor by the polling that check_queue() does.

Function that counts down as a subprocess

I have a problem in which I am running a Tkinter GUI program (a quiz game). While the user has a choice of 4 buttons and can choose one, I need a countdown timer that will change the question when the time is zero. I need it as a subprocess or separate thread because the user will not be able to choose an answer otherwise.
This is different from other questions about timers because the answers to those questions include [Object] = threading.Timer(numCount, callback), but the Timer does not return its value as it counts.
Is there any way to do this? I have already tried multiple methods, including the threading module, and the pygame clock (:D).
Multithreading might not be necessary: You can use the after method to change the question when time has passed, while keeping your GUI reactive:
In the following example, the question changes every 10 seconds.
import tkinter as tk
def countdown(t):
cdn['text'] = f'{t}'
if t > 0:
root.after(1000, countdown, t-1)
def change_question(idx):
lbl['text'] = questions[idx % 2]
root.after(10000, change_question, idx+1)
countdown(10)
def clickme(t):
print(f"{lbl['text']} : {t}")
if __name__ == '__main__':
questions = ['Is multi-threading necessary?', 'Is simple better than complicated?']
root = tk.Tk()
bt1 = tk.Button(root, text='Yes', command=lambda: clickme('Yes'))
bt2 = tk.Button(root, text='No', command=lambda: clickme('No'))
bt3 = tk.Button(root, text='Maybe', command=lambda: clickme('Maybe'))
bt4 = tk.Button(root, text='No Idea', command=lambda: clickme('No Idea'))
lbl = tk.Label(root, text='')
cdn = tk.Label(root, text='')
cdn.pack()
lbl.pack()
bt1.pack()
bt2.pack()
bt3.pack()
bt4.pack()
change_question(0)
root.mainloop()
sample output:
Is multi-threading necessary? : No
Is simple better than complicated? : Yes
You can use signal:
def myfunc(sig, frame):
print("timer fired")
signal.signal(signal.SIGALRM, myfunc)
signal.alarm(4) # seconds

How to change tkinter label while another process is running?

I have a large code where a button press is supposed to run a code that will take roughly 15 seconds to complete. Within this time I want to display a label that says "Processing, please wait" or something of that sort. However in python, the whole GUI created using tkinter will freeze and unfreeze once the procedure is over. How do I get around to doing this? I created a smaller code so that I can explain easier.
from tkinter import *
from threading import Thread
import os
import sys
import time
master = Tk()
master.geometry("500x500")
master.resizable(False,False)
def tryout():
sign2.config(text = "AAA")
for x in range(5):
print(x)
time.sleep(1)
sign2.config(text = "BBB")
for x in range(5):
print(x)
time.sleep(1)
sign2.config(text = "CCC")
def close_window():
master.destroy()
sys.exit()
sign1 = Label(master, text = "VNA GUI").grid(pady=10, padx=10)
sign2 = Label(master, text = "Choose option to continue")
sign2.grid(pady=10, padx=10, ipadx=50)
Button(master, text='Exit', command=close_window).grid(pady=10, padx=20)
butTest = Button(master, text='test', command=tryout)
butTest.grid(pady=10, padx=20)
master.mainloop( )
So in this code I expect to see 'AAA' on the label first, followed by 'BBB' at the middle of the count from 0 to 4, and then 'CCC' at the end of the final count from 0 to 4. What happens here is the GUI freezes at the beginning, the count carries on and I just see 'CCC'. How do I get around this?
There are only a few changes necessary to do that with threading.
First create a function start_tryout:
def start_tryout():
Thread(target=tryout, daemon=True).start() # deamon=True is important so that you can close the program correctly
Then create the button with the new command:
butTest = Button(master, text='test', command=start_tryout)
Then it should no longer freeze the gui and you should be able to see the label change.
You can try threading. I've made changes below to the code and tested it here, and it worked.
from tkinter import *
from threading import Thread
import os
import sys
import time
import threading # NEW
master = Tk()
master.geometry("500x500")
master.resizable(False,False)
def tryout():
sign2.config(text = "AAA")
for x in range(5):
print(x)
time.sleep(1)
sign2.config(text = "BBB")
for x in range(5):
print(x)
time.sleep(1)
sign2.config(text = "CCC")
def close_window():
master.destroy()
sys.exit()
def thread(): # NEW
threading.Thread(target=tryout).start() # NEW
sign1 = Label(master, text = "VNA GUI").grid(pady=10, padx=10)
sign2 = Label(master, text = "Choose option to continue")
sign2.grid(pady=10, padx=10, ipadx=50)
Button(master, text='Exit', command=close_window).grid(pady=10, padx=20)
butTest = Button(master, text='test', command=thread) # Changed
butTest.grid(pady=10, padx=20)
master.mainloop( )

Categories

Resources