Tkinter ask a string inside a thread - python

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.

Related

Ensure thread running independently of tkinter mainloop doesn't access an element which no longer exists

I'm creating a tkinter application and I want to disable certain GUI elements while a particular set of threads are running. Basically one button starts up all the threads and they get added to a list which is a member of a class named Form. I have a separate thread which is a class member and is constantly checking the list of threads to see if it is empty; as long as there is at least one living thread in the list, the GUI elements should be disabled.
class Form()
def __init__(self):
...
self.check_running_threads_thread = threading.Thread(target = self.check_running_threads)
self.check_running_threads_thread.start()
The issue is that I need this thread to quit running on application exit. The way I have been handling that is like so in main:
root = tk.Tk()
myForm = Form(root)
root.mainloop()
myForm.kill_threads = True
and the thread function that is started on the call of __init()__ of the Form class is:
def check_running_threads(self):
while not self.kill_threads:
for key, val in self.items.items():
if len(self.running_threads) > 0:
if key.endswith("entry"):
val.config(state='readonly')
elif key.endswith('button'):
val['state'] = tk.DISABLED
else:
if key.endswith("entry"):
val.config(state='normal')
elif key.endswith('button'):
val['state'] = tk.NORMAL
self.running_threads = [th for th in self.running_threads if th.is_alive()]
time.sleep(0.1)
where self.items is just a dictionary containing all the tk gui elements, indexed by a unique name.
The problem is that sometimes when the mainloop ends, such as by me exiting out of the application, the check_running_threads() function could be anywhere in the loop before kill_threads gets set to True, meaning it could try to change the state of the tk gui elements when they no longer exist. This leads to the program freezing and never exiting. The tk window is destroyed, but the command line window hangs forever.
What is the proper way to handle this? How can I ensure a thread running independently of the tkinter mainloop doesn't access an element which no longer exists?
2022-09-05 Edit:
Here is a complete minimal example:
import tkinter as tk
from tkinter import ttk
import threading
import time
import random
class Form():
def __init__(self, root):
self.kill_threads = False
self.running_threads = []
self.items = {}
self.btn = ttk.Button(root, text='Press me, wait for num threads to be zero, then click "X" button', command=self.run_threads)
self.btn.pack(side=tk.LEFT)
self.items['my_button'] = self.btn
for jj in range(60):
new = ttk.Button(root, text='', width=0)
self.items[f'my_{jj}_button'] = new
new.pack(side=tk.LEFT)
self.check_running_threads_thread = threading.Thread(target = self.check_running_threads)
self.check_running_threads_thread.start()
def thread_func(self):
time.sleep(random.uniform(0.5, 2.5))
def run_threads(self):
for ii in range(30):
mythread = threading.Thread(target = self.thread_func)
mythread.start()
self.running_threads.append(mythread)
def check_running_threads(self):
while not self.kill_threads:
for key, val in self.items.items():
if key.endswith("button"):
if len(self.running_threads) > 0:
val['state'] = tk.DISABLED
else:
val['state'] = tk.NORMAL
print("Num threads running:", len(self.running_threads))
self.running_threads = [th for th in self.running_threads if th.is_alive()]
time.sleep(0.2)
root = tk.Tk()
myForm = Form(root)
root.mainloop()
myForm.kill_threads = True
All the excess buttons are there to represent other GUI elements that are in my actual program. The more there are, the more likely it seems the program hangs on exit.
Here's a video of me running it. You can see the behavior is sometimes inconsistent (it doesn't always hang, such as the third time I ran it):
https://youtu.be/JhKxRYVujQg
You can create a new method in your Form class that will be called on close:
root = tk.Tk()
myForm = Form(root)
root.protocol("WM_DELETE_WINDOW", myForm.wait_till_threads_killed)
root.mainloop()
Where you method would look something like this:
def wait_till_threads_killed(self):
self.kill_threads = True
self.check_running_threads_thread.join()
for t in self.running_threads:
t.join()
self.parent.destroy()
After having added root as parent in the __init__ method:
def __init__(self, root):
self.parent = root
Edit: adding destroy() otherwise the window won't close + waiting for all threads
Edit2: note that this is only needed if you want to wait for the threads to finish. If want to kill them on exit, just set them as daemons in your initial code (then you don't necessarily need protocol):
mythread = threading.Thread(target = self.thread_func, daemon=True)

Using Ping constantly to verify connection and display feedback on python tkinter

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.

Use Tkinter After To Get Return Value And Return It Again

I needed a delay before calling a function and getting its return value. But time.sleep is freezing the tkinter GUI so i used tkinter.after. tkinter.after is working and won't freeze the window, but I cannot get the return value of the function that I've called. Because after I delayed and got the returned value, I've to return it again to the other function that called this function.
I've been struggling with this, please if any of you know any solutions, help me
This is the basic example of whats going on
import tkinter as tk
from time import sleep
def getvalue():
value = "haha"
sleep(3)
return value
def printvalue():
value = getvalue()
print(value)
app = tk.Tk()
app.geometry("500x300")
button = tk.Button(app, text="print value", command=printvalue)
button.pack()
app.mainloop()
For this simple example I would use tkinter.after() to run function with delay. And all code afer getting data I would move to second function which is executed by tkinter.after().
But your real code can be more complex and it can be hard to split it.
import tkinter as tk
def getvalue():
return "haha"
def second_part(other):
print('after delay')
value = getvalue()
# code moved from first part
print('value:', value)
print('other:', other)
button['text'] = value
def print_value():
# first part makes some calculation
other_variable = 'some value'
print('before delay')
# run function with delay and send all data from first part
app.after(3000, second_part, other_variable)
# rest of code moved to second_part
app = tk.Tk()
button = tk.Button(app, text="print value", command=print_value)
button.pack()
app.mainloop()
EDIT: If you have problem to use tkinter.after() then you can try to use Thread to run print_value which will have to wait for getvalue().
But Thread sometimes may have problem to access GUI widgets in main thread.
import tkinter as tk
from time import sleep
import threading
def getvalue():
sleep(3)
return "haha"
def print_value():
# first part makes some calculation
other_variable = 'some value'
print('before delay')
value = getvalue()
print('after delay')
print('value:', value)
print('other:', other_variable)
button['text'] = value
def start_thread():
t = threading.Thread(target=print_value)
t.start()
app = tk.Tk()
button = tk.Button(app, text="print value", command=start_thread)
button.pack()
app.mainloop()
I was thinking about asyncio which has non-blocking asyncio.sleep() but asyncio needs to run own loop which would block mainloop() so it would have to run in Thread.

Tkinter python multithread (avoid "Not Responding")

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.

GUI programming using Tkinter Python

I have a Tkinter GUI having 2 entry fields, 2 buttons ( initialization of these not shown in code). There is one more button (initialized in code) which performs the main task of performing change detection on two images. Also there is a progress bar.
Now, when the task of change detection has been completed, I want to display the 4 images(pre, post, aligned, chng) returned by wave.changedetection() in a separate Tkinter window. I want the new window to come only after changedetection() has completed.(wave.py is my own file, not some module)
Unfortunately, if I try to add code to make new window, Tk.Toplevel() ,after the wave.changedetection() call, nothing happens and the main GUI window becomes unresponsive and has to be killed.
There is no way to know when the new created thread (start_thread)completes it's work, so that I can do Tk.Toplevel() there.
How can I do what I require?
class GUI(Tkinter.Tk):
def __init__(self, parent)
Tkinter.Tk.__init__(self, parent)
self.parent = parent
self.initialize()
def initialize(self):
self.button = Tkinter.Button(text = "Start")
self.button.bind('<Button-1>', self.OnButtonClick)
self.button.pack()
self.int = Tkinter.IntVar()
self.pgbar = Tkinter.ProgressBar(variable = self.int, mode = determinate)
def OnButtonClick(self,event):
#this func has been made since I have more buttons.It may seem redundant here
self.button['command'] = self.start_thread()
self.update_idletasks()
def start_thread(self):
self.int_var.set(1)
q = queue.Queue()
self.secondary_thread = threading.Thread(target = self.change)
self.secondary_thread.start()
self.after(50, self.check_queue, q)
def check_queue(self, q):
while True:
try:
x = wave.q.get_nowait()
except queue.Empty :
self.after(50,self.check_queue,q)
break
else:
self.int_var.set(x)
if x == 6:
self.button3['state'] = 'normal'
break
def change(self):
'''The 6 functions of wave.changedetection() change the value of self.int
due to which progress bar progresses.'''
pre, post, aligned, chng = wave.changedetection(self.entry_1.get(),
self.entry_2.get())
if __name__ == '__main__':
gui = GUI(None)
gui.mainloop()
code to update progress bar taken from here (2nd answer,Honest Abe's answer)
You have to be able to differentiate name spaces, i.e. this is in the main window and this is in the Toplevel. I would suggest that you get the Toplevels working first and then decide if you want to add threading or not. The code below is a simple example of creating Toplevels and shows how to place widgets in a specific name space (window in this case). You may or may not want a separate "create a Toplevel" class if there are functions you want to associate with each Toplevel's namespace. Also there are examples on the web on using Tkinter's "after" to update a progressbar. That is a different question so start another thread if you have questions about the progressbar.
try:
import Tkinter as tk ## Python 2.x
except ImportError:
import tkinter as tk ## Python 3.x
from functools import partial
class OpenToplevels():
""" open and close additional Toplevels with a button
"""
def __init__(self):
self.root = tk.Tk()
self.button_ctr=0
## in the "root" namespace *********************
but=tk.Button(self.root, text="Open a Toplevel",
command=self.open_another)
but.grid(row=0, column=0)
tk.Button(self.root, text="Exit Tkinter", bg="red",
command=self.root.quit).grid(row=1, column=0, sticky="we")
self.root.mainloop()
def close_it(self, id):
## destroy the window in this id's namespace ***********
id.destroy()
## id.withdraw()
## id.iconify()
def open_another(self):
self.button_ctr += 1
id = tk.Toplevel(self.root)
id.title("Toplevel #%d" % (self.button_ctr))
## in the "id for this Toplevel" namespace ***********
tk.Button(id, text="Close Toplevel #%d" % (self.button_ctr),
command=partial(self.close_it, id),
bg="orange", width=20).grid(row=1, column=0)
Ot=OpenToplevels()

Categories