Finally got my first threading script "somewhat" running after going back and forth between various tutorials and online references. "Somewhat" because I am still getting some curious error during startup.
My program objective
- Integral up/down counter GUI constructed using tkinter. (Python 3.4.2)
- Spinbox as user entry point - User either enters a "target" integer or clicks on its up/down arrow.
- Show delayed updates on a label as the number on the label ramps up/ down to meet the "targeted" integer mentioned in (2)
- Updating automatically stops when the number displayed on the label equals the targeted value
- A "Go" button to starts the ramp up/ down after the user entered the targeted value
- A "Stop" button to stop the update pause the ramp up/ down anytime before reaching the targeted value
In this simplified implementation for debugging, you will see that I seemingly used the spinbox control variable unnecessarily. The actual (longer program) the control variable shall be used to trigger a validation routine for the user's input into the spinbox. And it is actually the control variable that is giving me some "curious error" during program start up
My simplified codes are as follows:
import tkinter as tk
import threading
import time
class controlPanel():
def __init__(self, master):
self.root = master
self.buildWidgets()
def buildWidgets(self):
self.labelVar = 0
self.ctrlVar = tk.StringVar()
self.ctrlVar.set(str(self.labelVar))
self.delay = 0.5
#+++++++++++++++++++++++++++++++++
self.labelName = tk.Label(self.root, text="Current Value: ", padx=3, pady=3)
self.labelName.grid(row=0, column=0)
self.labelValue = tk.Label(self.root, text=str(self.labelVar), padx=3, pady=3)
self.labelValue.grid(row=0, column=1)
#+++++++++++++++++++++++++++++++++
self.spinboxName = tk.Label(self.root, text="Target: ", padx=3, pady=3)
self.spinboxName.grid(row=1, column=0)
self.spinBoxA = tk.Spinbox(self.root, from_=0, to=1000,
textvariable=self.ctrlVar,
width=10, justify=tk.CENTER)
self.spinBoxA.grid(row=1, column=1)
#+++++++++++++++++++++++++++++++++
self.goButton = tk.Button(self.root, text="Go", width=12,
command=self.goButtonFunction,
padx=3, pady=3)
self.goButton.grid(row=2, column=1)
#+++++++++++++++++++++++++++++++++
self.stopButton = tk.Button(self.root, text="Stop", width=12,
command=self.stopButtonFunction,
padx=3, pady=3)
self.stopButton.grid(row=2, column=0)
#+++++++++++++++++++++++++++++++++
#self.labelValue.update()
#self.spinBoxA.update()
self.root.update()
def goButtonFunction(self):
print('GO button clicked')
self.flgRun = True
def stopButtonFunction(self):
print('STOP button clicked')
self.flgRun = False
class controlThread(controlPanel):
def __init__(self, master, name):
self.root = master
self.name = name
controlPanel.__init__(self, self.root)
self.flgRun = False
self.flgRunLock = threading.Lock()
self.pollPeriod = 100 # polling period, in ms
self.thread1 = threading.Thread(target = self.towardsTarget,
name=self.name)
self.thread1.daemon = False
self.thread1.start()
#time.sleep(5)
self.pollFlgRun()
#+++++++++++++++++++++++++++++++++
def pollFlgRun(self): # polls self.flgRun every self.pollPeriod
if self.flgRun:
print('<< Entering pollFlgRun >>')
time.sleep(0.01)
self.towardsTarget()
#self.flgRunLock.acquire() # lock thread to reset self.flgRun
self.flgRun = False # reset self.flgRun
#self.flgRunLock.release() # release thread
self.root.after(self.pollPeriod, self.pollFlgRun)
#+++++++++++++++++++++++++++++++++
def towardsTarget(self):
delay = 0.01 # delay in seconds
time.sleep(delay)
print('<< Entering towardsTarget >>')
#self.flgRunLock.acquire()
print('self.labelVar : ', str(self.labelVar))
# Problem 1: the following reference to self.ctrlVar gave error everytime the
# program starts. The error>> "RuntimeError: main thread is not in main loop"
# subsequent click on controlPanel.goButton will still give the program behavior
# specified above (i.e. no more error on subsequent clicks on the "Go" button)
#
# Problem 2: and when placed in a lock.acquire()/release() block, clicking the
# controlPanel.goButton will freeze up program
print('self.ctrlVar : ', self.ctrlVar.get())
#self.flgRunLock.release()
# test self.flgRun as well in case reset by controlPanel.stopButton
while( self.flgRun and str(self.labelVar) != self.ctrlVar.get() ):
print('WHILE loop')
if( self.labelVar < int(self.ctrlVar.get()) ):
self.labelVar +=1
print('self.labelVar before sleep> ', self.labelVar)
time.sleep(delay)
print('self.labelVar AFTER sleep> ', self.labelVar)
self.labelValue["text"] = str(self.labelVar)
self.labelValue.update()
else:
self.labelVar -=1
print('self.labelVar before sleep> ', self.labelVar)
time.sleep(delay)
print('self.labelVar AFTER sleep> ', self.labelVar)
self.labelValue["text"] = str(self.labelVar)
self.labelValue.update()
if __name__ == '__main__':
root = tk.Tk()
ctrl = controlThread(root, "X-thread")
Problem 1:
- Within
class controlThread.towardsTarget()
theprint
statement referencingself.ctrlVar
gave rise to error everytime the program starts. The error being "RuntimeError: main thread is not in main loop". - Subsequent click on the "Go" button (
controlPanel.goButton
) will still give the program behavior specified above (i.e. no more error on subsequent clicks on the "Go" button)
Problem 2:
- when the
print
statement mentioned in Problem 1 is placed in alock.acquire()/lock.release()
block, clicking the "Go" button (controlPanel.goButton
) will freeze up program
I've read the following two pages and the links associated
- Threaded Tkinter script crashes when creating the second Toplevel widget
- RuntimeError: main thread is not in main loop
But the solutions mentioned in the two pages above did not make much sense to me as the error message "RuntimeError: main thread is not in main loop" does not appear at all if the mentioned print
statement referencing self.ctrlVar
was removed altogether.
My questions are
- What is causing this error in Problem 1?
- I was expecting the
lock.acquire()/lock.release()
block to solve the problem but it ended up freezing the program instead. How can the problem be avoided with the 'print' statement referencingself.ctrlVar
in place?