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() the print statement
referencing self.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 a
lock.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 referencing
self.ctrlVar in place?
Related
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.
I am currently trying to program a robot arm which is controlled by a Raspberry Pi.
Everything works fine so far, except for one thing and I already googled and tried everything for many hours but can't find a working solution.
For the movement of the robot arm it is necessary to run all motors "simultaneously" with threads (works fine).
The problem I have is that I need to update a label which shows the current angle of an axis (motor) as soon as it finished its movement but other motors are still running (threads).
After a lot of research I thought I found the solution by using a queue and Tkinters after-method. But it still doesn't work as the labels text only gets updated after all threads terminated.
I wrote an example code where I want to get a label update for motor "one" which will finish its for-loop (100 iterations) before motor "two" (500 iterations). I expected the label to get updated as soon as motor one reached its target while motor two is still runing.
But although I used the after-method it still waits till motor two finished before updating the label.
Hope you can help me!
from tkinter import *
import threading
import time
from queue import *
class StepperMotors:
def __init__(self, root):
self.root = root
self.start_btn = Button(root, text="Start", command=lambda:self.start_movement())
self.start_btn.config(width = 10)
self.start_btn.grid(row=1,column=1)
self.label_one = Label(root, text='')
self.label_one.config(width = 10)
self.label_one.grid(row=2, column=1)
self.label_two = Label(root, text='')
self.label_two.config(width = 10)
self.label_two.grid(row=3, column=1)
def start_movement(self):
self.thread_queue = Queue()
self.root.after(100, self.wait_for_finish)
thread_one = threading.Thread(target=self.motor_actuation, args=(1,100))
thread_two = threading.Thread(target=self.motor_actuation, args=(2,500))
thread_one.start()
thread_two.start()
thread_one.join()
thread_two.join()
def motor_actuation(self, motor, iterations):
for i in range(iterations):
i = i+1
update_text = str(motor) + " " + str(i) + "\n"
print(update_text)
time.sleep(0.01)
self.thread_queue.put(update_text)
def wait_for_finish(self):
try:
self.text = self.thread_queue.get()
self.label_one.config(text=self.text)
except self.thread_queue.empty():
self.root.after(100, self.wait_for_finish)
if __name__ == "__main__":
root = Tk()
root.title("test")
stepper = StepperMotors(root)
root.mainloop()
It’s better to use daemon thread which are non-blocking.
Also, It’s better to have a separation of concerns: a robot (or robot arm) can be a object which has its own life time: a daemon thread. Idem, you can define a "LabelUpdater" which read the state of a robot and update a label.
Let’s define a robot:
it is created on application initialisation and is run when the user click the "Start" button,
The robot moves and reports its angle in a app-level multithreading-queue,
class Robot(threading.Thread):
def __init__(self, name: str, label_queue: queue.Queue, end_pos: int):
super().__init__(name=name)
self.daemon = True
self.label_queue = label_queue
self.end_pos = end_pos
def run(self) -> None:
for angle in range(self.end_pos):
self.label_queue.put(angle)
time.sleep(0.01)
Let’s define a LabelUpdater:
It is created on application initialisation and run forever (it can observe a robot even if it is not running).
It reads the robot queue (for instance each second to avoid blinking) and update the label
class LabelUpdater(threading.Thread):
def __init__(self, name: str, label_queue: queue.Queue, root_app: tkinter.Tk, variable: tkinter.Variable):
super().__init__(name=name)
self.daemon = True
self.label_queue = label_queue
self.root_app = root_app
self.variable = variable
def run(self) -> None:
# run forever
while True:
# wait a second please
time.sleep(1)
# consume all the queue and keep only the last message
last_msg = None
while True:
try:
msg = self.label_queue.get(block=False)
except queue.Empty:
break
last_msg = msg
self.label_queue.task_done()
if last_msg:
self.variable.set(last_msg)
Then, the main application should define:
2 multithreading queues: one for each label,
2 tkinter.StringVar variable which will be updated,
the robots and the label updaters,
the two updaters are started, and will run forever.
class StepperMotors:
def __init__(self, root):
self.root = root
self.label_one_queue = queue.Queue()
self.label_two_queue = queue.Queue()
self.start_btn = tkinter.Button(root, text="Start", command=lambda: self.start_movement())
self.start_btn.config(width=10)
self.start_btn.grid(row=1, column=1)
self.text_one = tkinter.StringVar()
self.text_one.set("one")
self.label_one = tkinter.Label(root, textvariable=self.text_one)
self.label_one.config(width=10)
self.label_one.grid(row=2, column=1)
self.text_two = tkinter.StringVar()
self.text_two.set("two")
self.label_two = tkinter.Label(root, textvariable=self.text_two)
self.label_two.config(width=10)
self.label_two.grid(row=3, column=1)
self.robot_one = Robot("robot_one", self.label_one_queue, 100)
self.robot_two = Robot("robot_two", self.label_two_queue, 500)
self.updater_one = LabelUpdater("updater_one", self.label_one_queue, self.root, self.text_one)
self.updater_two = LabelUpdater("updater_two", self.label_two_queue, self.root, self.text_two)
self.updater_one.start()
self.updater_two.start()
def start_movement(self):
self.robot_one.start()
self.robot_two.start()
Of course, you need a flag or something to check that each robot is not already running.
My code currently checks the username and password entered my the user and then returns to the label with the corresponding text.
As shown below:
from tkinter import *
def Login():
global AnameEL
global ApwordEL # More globals :D
global ArootA
global f1
global f2
ArootA = Tk() # This now makes a new window.
ArootA.geometry('1280x720')
ArootA.title('Admin login') # This makes the window title 'login'
f1 = Frame(width=200, height=200, background="#D3D3D3")
f2 = Frame(ArootA, width=400, height=200)
f1.pack(fill="both", expand=True, padx=0, pady=0)
f2.place(in_=f1, anchor="c", relx=.5, rely=.5)
AnameL = Label(f2, text='Username: ') # More labels
ApwordL = Label(f2, text='Password: ') # ^
AnameL.grid(row=1, sticky=W)
ApwordL.grid(row=2, sticky=W)
AnameEL = Entry(f2) # The entry input
ApwordEL = Entry(f2, show='*')
AnameEL.grid(row=1, column=1)
ApwordEL.grid(row=2, column=1)
AloginB = Button(f2, text='Login', command=CheckLogin) # This makes the login button, which will go to the CheckLogin def.
AloginB.grid(columnspan=2, sticky=W)
ArootA.mainloop()
def CheckLogin():
checkP = Label(f2, text='')
checkP.grid(row=3, column=1)
if AnameEL.get() == "test" and ApwordEL.get() == "123": # Checks to see if you entered the correct data.
checkP.config(text='sucess')
else:
checkP.config(text='fail')
Login()
I would like to add another feature where after 2 seconds new lines of code are ran depending on the login failed/success.
For example when the user enters a wrong login I would like the text "fail" to disappear after 2 seconds and if the user enters the correct password I would like a new function to be ran after 2 seconds of the "success" being displayed.
So I tried this:
(also importing time at the top of my code)
if AnameEL.get() == "test" and ApwordEL.get() == "123": # Checks to see if you entered the correct data.
checkP.config(text='sucess')
time.sleep(2)
nextpage()
else:
checkP.config(text='fail')
time.sleep(2)
checkP.config(text='')
def nextpage():
f1.destroy()
However, this wasn't successful. After the login button was pressed it waited 2 seconds and then ran nextpage() instead of displaying "success" for 2 seconds and then running nextpage() and for incorrect logins it goes straight to checkP.config(text='') after 2 seconds of the button press.
How can I resolve this?
All help is appreciated,
Thanks.
You need to update root before using time.sleep(). Additionally, since you are dealing with a GUI, you should prefer using timers over pausing execution. In this case, Tkinter's own after() function should be preferred over time.sleep(), because it simply places the event on the event queue as opposed to pausing execution.
after(delay_ms, callback=None, *args)
Registers an alarm callback that is called after a given time.
So, per your example:
if AnameEL.get() == "test" and ApwordEL.get() == "123":
checkP.config(text='sucess')
ArootA.update()
time.sleep(2)
nextpage()
else:
checkP.config(text='fail')
ArootA.update()
time.sleep(2)
nextpage()
With after():
if AnameEL.get() == "test" and ApwordEL.get() == "123":
checkP.config(text='sucess')
ArootA.after(2000, nextpage)
else:
checkP.config(text='fail')
ArootA.after(2000, lambda : checkP.config(text=''))
You may also want to take a look at alternative ways to update the values of labels to avoid having to update root while you are in the mainloop (e.g. Making python/tkinter label widget update?).
Hi I have a small python gui interface with two buttons, start(That starts a counter) and stop (that is suppose to stop the counter), the counter is an infinite loop since I do not want it to end unless the second button is clicked. The problem is the second button cannot be clicked while the function from the first one is still running.
I read that I need to use threading and I have tried but I do not fully understand how I can do this. Please help.
from Tkinter import *
import threading
class Threader(threading.Thread):
def run(self):
for _ in range(10):
print threading.current_thread().getName()
def main(self):
import itertools
for i in itertools.count(1, 1):
print i
def other(self):
print "Other"
m = Threader(name="main")
o = Threader(name="other")
try:
'''From here on we are building the Gui'''
root = Tk()
'''Lets build the GUI'''
'''We need two frames to help sort shit, a left and a right vertical frame'''
leftFrame = Frame(root)
leftFrame.pack(side=LEFT)
rightFrame = Frame(root)
rightFrame.pack(side=RIGHT)
'''Widgets'''
'''Buttons'''
playButton = Button(leftFrame, text="Play", fg="blue", command=m.main)
stopButton = Button(rightFrame, text="Stop", fg="red", command=o.other)
playButton.pack(side=TOP)
stopButton.pack(side=BOTTOM)
root.mainloop()
except Exception, e:
print e
Here's a short example of using threading. I took out your other function and I don't know why your using itertools here. I took that out as well and simply setup using a simple threading example.
A few things:
You setup using threading.Thread as the base class for Threader, but you never actually initialized the base class.
Whenever you use threading you generally want to define a run method and then use start() to start the thread. Calling start() will call run.
You need to use threading to prevent your GUI blocking, because tkinter is just one thread on a giant loop. So, whenever you have some long running process it blocks this thread until the current process is complete. That's why it's put in another thread. Python has something called the GIL, which prevent's true parallelization (I made up that word) since it only one thread can ever be used at a time. Instead, it uses time slicing, the GIL sort of "polls" between them to give the appearance of multiple tasks running concurrently. For true parallel processing you should use multiprocessing.
In the below code I have used self.daemon = True. Setting the thread to be a daemon will kill it when you exit the main program (In this case the Tk GUI)
from tkinter import *
import threading, time
class Threader(threading.Thread):
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self, *args, **kwargs)
self.daemon = True
self.start()
def run(self):
while True:
print("Look a while true loop that doesn't block the GUI!")
print("Current Thread: %s" % self.name)
time.sleep(1)
if __name__ == '__main__':
root = Tk()
leftFrame = Frame(root)
leftFrame.pack(side=LEFT)
rightFrame = Frame(root)
rightFrame.pack(side=RIGHT)
playButton = Button(leftFrame, text="Play", fg="blue",
command= lambda: Threader(name='Play-Thread'))
stopButton = Button(rightFrame, text="Stop", fg="red",
command= lambda: Threader(name='Stop-Thread'))
playButton.pack(side=TOP)
stopButton.pack(side=BOTTOM)
root.mainloop()
For something as simple as a counter, Tkinter's after() method is usually a better choice. You can use an instance variable to set it to on and off.
class TimerTest():
def __init__(self, root):
self.root=root
Button(root, text="Play", fg="blue",
command=self.startit).grid(row=1, column=0)
Button(root, text="Stop", fg="red",
command=self.stopit).grid(row=1, column=1)
self.is_running=True
self.count=IntVar()
Label(root, textvariable=self.count,
bg="lightblue").grid(row=0, column=0, columnspan=2, sticky="ew")
def startit(self):
self.is_running=True
self.increment_counter()
def increment_counter(self):
if self.is_running:
c=self.count.get()
c += 1
self.count.set(c)
root.after(1000, self.increment_counter) ## every second
def stopit(self):
self.is_running = False
root = Tk()
TT=TimerTest(root)
root.mainloop()
How to get the value from tkinter toplevel in side a 0 attribute function?
def Update():
Up = Toplevel()
Up.title("Update")
taskLabel = Label(Up, text ="Update", font=('Times', 20)).grid(row=0)
var = IntVar()
radio = Radiobutton(Up, text="Fully", variable=var, value=1, command = taskUpdate).grid(row=2, sticky=W)
radio = Radiobutton(Up, text="Partly", variable=var, value=2, command = taskUpdate).grid(row=3, sticky=W)
radio = Radiobutton(Up, text="Un", variable=var, value=3, command = taskUpdate).grid(row=4, sticky=W)
note = EntryaUp, width=30, font=('Arial', 12,)).grid(row=6)
Button = Button(Up, text ="Task Complete and Send Invoice", command = taskUpdate).grid(row =7)
return (var, note)
def updateB ():
var, noteBox = Update()
a = (var.get())
b = (note.get())
This is a top level window in tkinter and I want to get the value 1,2,3 when I click the radio button and get the string value when I enter text in the entry and click the command button, but I just have no idea how can I do that. By the way, I don't think I can return the value at all, every time when its return it only return it initial value which is 0 and " ". I know how it works and can get the value if the radiobutton is not in the top level (or in a function). But I could not figure it out how can I do it if the struture is like this.
The short answer to your question is that you can use wait_window() to wait for the window to be dismissed, and then you can return whatever you want from your function. You would then hook up your "Task complete" button to simply destroy the window.
You have to be very careful here -- wait_window creates a new, nested event loop. Most often this is done in conjunction with a window grab to prevent events in other windows from being processed. When you do this, you are creating a modal dialog.
Here's a working example; I had to tweak your code a little, and I took the liberty of removing some unnecessary variables (such as references to the radiobuttons, since you never do anything with the references)
import Tkinter as tk # python 2.7
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
button = tk.Button(self, text="Open window", command=self.on_button)
button.pack()
def on_button(self):
var, note = Update()
a = var.get()
b = note.get()
print "a: '%s' b: '%s'" % (a,b)
def Update():
Up = tk.Toplevel()
Up.title("Update")
tk.Label(Up, text ="Update", font=('Times', 20)).grid(row=0)
var = tk.IntVar()
tk.Radiobutton(Up, text="Fully", variable=var, value=1).grid(row=2, sticky="w")
tk.Radiobutton(Up, text="Partly", variable=var, value=2).grid(row=3, sticky="w")
tk.Radiobutton(Up, text="Un", variable=var, value=3).grid(row=4, sticky="w")
evar = tk.StringVar()
note = tk.Entry(Up, width=30, font=('Arial', 12,), textvariable=evar)
note.grid(row=6)
Button = tk.Button(Up, text ="Task Complete and Send Invoice", command = Up.destroy).grid(row =7)
Up.wait_window()
print "done waiting..."
return (var, evar)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
To see why you must be careful when using this function, try the following scenarios:
click on the "Open Window" button, fill in the form and click on the "Task Complete" button. Notice that it probably works the way you expect (it prints to stdout, so make sure you're running from a console window)
click on the "Open Window" button, then click on it again. You now have two windows. Fill in the second window, click on "Task Complete" and notice it works like you expect. Now fill in the first window and click on "Task Complete" and it still works just fine. In both cases it prints the correct values to the screen when you click the button.
click on the "Open Window" button, then click on it again. You now have two windows. This time, fill in the first window and click "Task Complete". Notice that it does not print the results to the screen. This is because the nested event loop of the first window can't complete until the nested event loop of the third window completes. Fill in the form of this second window and click on "Task Complete" and you'll see that the two nested event loops unwind, but not in the order that you clicked on "Task Complete" but rather in the order that the windows were created.
This is why you need to be careful when using wait_window - you should prevent this recursive nesting of event loops or your GUI may not behave the way you expect.