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.
Related
I'm trying to run a multi thread application,
each thread is asking for the user to provide a string input after some tasks the thread does.
simpledialog.askstring(title, content, parent=window)
the problem is that you cannot use the main thread window as the parent window inside a thread because you'll end up with this error:
_tkinter.TclError: window ".!_querystring2" was deleted before its visibility changed
which is logical because the thread and the main thread are not synchronized
after hard research I've ended up with a solution which says
"Create a new TK window instance for the thread"
newWin = tk.Tk()
newWin.withdraw() #to make it invisible
simpledialog.askstring(title, content, parent=newWin)
newWin.destroy()
which will work for my case if I wouldn't have a main tk window instance inside the main thread.
the destroy method for the "newWin" is causing my main window to be destroyed as well.
and I'll be ended up with this error :
Tcl_AsyncDelete: async handler deleted by the wrong thread
tkinter isn't a thread safe, and not gives an easy life when using it cross threads.
does anyone have any idea/trick how can I ask a string from a user inside a thread?
Some extra info about my code :
I'm triggering this function by a button click:
#tree is a treeview widget
def click(tree):
threads = simpledialog.askstring("Threads", "How many threads?")
if threads is not None:
try:
threads = int(threads)
if threads > 0:
thread = tm.threadObject(name="checkAccounts", target=checkUsers, args=[tree, threads])
thread.start()
#instance passes automatically from the tm.threadObject
def checkUsers(instance, tree, threads):
for child in tree.get_children():
while len(tm.threadsManager.getThreads("checkAccountWorker")) >= threads and instance.alive:
print("Sleeping")
time.sleep(1)
if not instance.alive:
break
item = tree.item(child)
taskHandler = task.taskHandler(tree, child)
thread = tm.threadObject(name="checkAccountWorker", target=taskHandler.doAction, args=[taskHandler.CHECK_USER], kill=taskHandler.kill, killargs=[], passInstance = False)
thread.start()
#part of the doAction code:
def doAction(*args):
#some code behind
newWin = tkinter.Tk()
newWin.withdraw()
code = tkinter.simpledialog.askstring("Activation", "Enter recevied code : ", parent=newWin)
newWin.destroy()
self.currentStatus = "Entering code"
mainEvents.updateTreeItem(tree, "status", item, self.currentStatus)
def updateTreeItem(tree, column, item, value):
key = ""
for col in tree["columns"]:
if tree.heading(col)["text"].lower() == column.lower():
key = col
if key == "":
return
tree.set(item, key, value)
It works at the first attempt. but when I click again on the checkUsers button I end up with the error :
"Tcl_AsyncDelete: async handler deleted by the wrong thread"
I honestly don't know why
Every time button is clicked we create new threading.Thread object and start it. It's target will be the askString function.
In the askString we create new window and add to it widget simpledialog.askstring.
This code below will do the trick:
import tkinter as tk
from tkinter import simpledialog
import threading
def askString():
new_window = tk.Tk()
new_window.withdraw()
print(simpledialog.askstring(title = "String input", prompt = "What's your Name?:", parent = new_window))
new_window.destroy()
root = tk.Tk()
btn = tk.Button(root, text = "Click me!", command = lambda: threading.Thread(target = askString).start())
btn.pack()
root.mainloop()
How you get the value from the function to your GUI is up to your
implementation (framework).
Okay,
After some work around I've created my own "getString" window dialog..
code:
#staticmethod
def getString(title, content):
topLevel = tk.Toplevel()
topLevel.title(title)
topLevel.resizable(False, False)
strVar = tk.StringVar()
label = tk.Label(topLevel, text=content)
label.grid(column=0, row=0, columnspan=2)
entry = tk.Entry(topLevel, textvariable=strVar)
entry.grid(column=0, row=1)
def close(topLevel):
if entry.get() != "":
topLevel.destroy()
button = tk.Button(topLevel, text="Set", command=lambda: close(topLevel))
button.grid(column=1, row=1)
topLevel.lift()
while(topLevel.winfo_exists()): # wait until topLevel destroyed
time.sleep(1)
return strVar.get()
It's a bit messy.. couldn't manage to accomplish a cleaner solution but it works.
You can ask for a string inside a thread this way :)
If someone have a nicer way to do so you may suggest.
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.
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
I'm trying to write a Python GUI program with tkinter.
I want to make two thread. One runing with the main_form function to keep tkinter from keep update and loop (avoid "Not Responding").
The other, when the button1 (btn1) is clicked make function sci_thread() start running and start thread2 that execute the main_scikit with long time code.
But tkinter keep Not Responding.
Below is my code:
import threading
class class_one:
def main_scikit(seft):
######
code_take_loooong_time
######
def save(seft):
pass
def main_form(seft):
root = Tk( )
root.minsize(width=300, height=500)
ent1 = Entry(width=30)
ent1.grid(row=0,column=1,padx = 10,pady=5)
bnt1 = Button(root,text = "Start",command=lambda : seft.sci_thread())
bnt1.grid(row=5,column=0,padx = 10)
root.update()
root.mainloop()
def sci_thread(seft):
maincal = threading.Thread(2,seft.main_scikit())
maincal.start()
co = class_one()
mainfz = threading.Thread(1,co.main_form());
mainfz.start()
Your app is unresponsive because your target parameter executed when declared and result of that passed as target. And, obviously, because of that you GUI is unresponsive while code_take_loooong_time being executed in GUI's thread. To deal with it - get rid of redundant parentheses.
Try this snippet:
import threading
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
class class_one:
def main_scikit(self):
######
# code_take_loooong_time
# same as sleep
threading.Event().wait(5)
# some print
self.print_active_threads_count()
######
def save(self):
pass
def main_form(self):
self.root = tk.Tk()
self.root.minsize(width=300, height=500)
self.ent1 = tk.Entry(self.root, width=30)
self.ent1.grid(row=0, column=1, padx=10, pady=5)
self.bnt1 = tk.Button(self.root, text="Start", command=self.sci_thread)
self.bnt1.grid(row=5, column=0, padx=10)
self.root.update()
self.root.mainloop()
def sci_thread(self):
maincal = threading.Thread(target=self.main_scikit)
maincal.start()
def print_active_threads_count(self):
msg = 'Active threads: %d ' % threading.active_count()
self.ent1.delete(0, 'end')
self.ent1.insert(0, msg)
print(msg)
co = class_one()
mainfz = threading.Thread(target=co.main_form)
mainfz.start()
Links:
Similar problem with a tkinter button's parameter
More general: How to pass a method as an parameter
Threading docs
P.S.:
Also, be careful when you start a tkinter application not in the main thread because tkinter expects (in general) that mainloop is outer-most loop possible and that all Tcl commands invoked from the same thread. So there can be many and more synchronisation problem with all that, even if you just trying to quit GUI!
In conclusion, maybe this and that would give you some new ideas.
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?