1

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.

martineau
  • 119,623
  • 25
  • 170
  • 301
Husar
  • 413
  • 1
  • 6
  • 17
  • not if you use a thread ... so im not sure what you mean by you tried using a thread – Joran Beasley Feb 20 '15 at 22:22
  • well if you were *waiting* for your thread to come back then yes, that would block just as much as doing it inline. Unfortunately I'm not familiar with TK (I discredit it for not being very pretty in stock); Maybe you could use some help on setting up the TK [event loop](http://stackoverflow.com/q/459083/1695680), that should let you do thing and handle UI events in real time without using sub-processes/threads. I've herd that the threading module might take a significant performance hit on multi-core hosts, but this might only apply to c extensions. – ThorSummoner Feb 20 '15 at 22:28

3 Answers3

4

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

Joran Beasley
  • 110,522
  • 12
  • 160
  • 179
  • Hmm, maybe I was doing the Thread wrong when I tried this. I am going to give this a go and see if it helps. Also, the message box was just a way to test this and not part of the read issue. I'll comment back after giving this a try. Thanks! – Husar Feb 20 '15 at 23:32
  • A big THANK YOU to Joran. This is exactly what I needed. I guess I was trying to make the thread way more complicated that I needed to. You example is exactly what I needed. I will post another example so other can see it. This now displays the count to a label to show that it does update the UI. – Husar Feb 20 '15 at 23:46
0

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.

Breedly
  • 12,838
  • 13
  • 59
  • 83
  • I will check out the tutorial but I don't think that will work for what I need. Here is a good example of what is happening in my application. A stop watch is started and is displayed on the screen as it runs mm:ss:hs. The application is then kicking off the While statement that is reading data. Right now what happen is the stop watch stops displaying anything while the while statement is executing. I can't believe that making the While pause long enough to update the screen is going to work without the stop watch visually skipping. I hope there is a better way. – Husar Feb 20 '15 at 23:27
  • there are a million better ways – Joran Beasley Feb 20 '15 at 23:36
0

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()
Husar
  • 413
  • 1
  • 6
  • 17