i have tkinter class and some functions in it, (assume all other functions are present to initiate the GUI). what i have done i have started one self.function as a thread from other self.function and in threaded function upon error i want to use tkMessageBox.showerror('Some Error') but this does not work in threaded function and my program got stuck. msgbox is working in other function.
import threading
from Tkinter import *
import Pmw
import tkMessageBox
class tkinter_ui:
def __init__(self, title=''):
... assume all functions are present ...
def login(self, username, password)
if password == "":
tkMessageBox.showerror('Login Error', 'password required') # but on this msg box program become unresponsive why???
def initiateLogin(self)
tkMessageBox.showinfo('Thread', 'Started') #you see this msg box works
self.t = threading.Timer(1, self.login)
self.t.start()
Since I got stuck on the same problem and didn't find a proper, well explained solution, I'd like to share a basic strategy I came out with.
Note that this is not the only nor the best way to do threading with tkinter, but it's quite straightforward and should preserve your workflow if you designed your code without being aware of tkinter's thread-unsafetiness.
Why threads?
First of all, I chose to use threads seeing that blocking actions like os.popen, subprocess.call, time.sleep and the like would "freeze" the GUI until they run (of course this may not be your case since threads are useful by their own for many reasons and sometimes they are just needed).
This is how my code looked like before using threads:
from Tkinter import *
import tkMessageBox
from time import sleep
# Threadless version.
# Buttons will freeze the GUI while running (blocking) commands.
def button1():
sleep(2)
tkMessageBox.showinfo('title', 'button 1')
def button2():
sleep(2)
tkMessageBox.showinfo('title', 'button 2')
root = Tk()
frame = Frame(root)
frame.pack()
Frame(root).pack( side = BOTTOM )
Button(frame, command=button1, text="Button 1").pack( side = LEFT )
Button(frame, command=button2, text="Button 2").pack( side = LEFT )
root.mainloop()
Buggy threaded version
Then I turned the commands called by the buttons into threads. This way, the GUI would not freeze.
I thought it was ok, but on Windows this code leads the interpreter to crash irreparably due to the tkMessageBoxes called from threads other than the one in which the tkinter's root is running:
from Tkinter import *
import tkMessageBox
from time import sleep
import threading
# Buggy threads.
# WARNING: Tkinter commands are run into threads: this is not safe!!!
def button1():
sleep(2)
tkMessageBox.showinfo('title', 'button 1')
def button2():
sleep(2)
tkMessageBox.showinfo('title', 'button 2')
def start_thread(fun, a=(), k={}):
threading.Thread(target=fun, args=a, kwargs=k).start()
root = Tk()
frame = Frame(root)
frame.pack()
Frame(root).pack( side = BOTTOM )
Button(frame, command=lambda: start_thread(button1), text="Button 1").pack( side = LEFT)
Button(frame, command=lambda: start_thread(button2), text="Button 2").pack( side = LEFT )
root.mainloop()
Thread-safe version
When I discovered the thread-unsafetiness of tkinter, I wrote a small function tkloop that would run in the main thread each few milliseconds checking requests and executing requested (tkinter) functions on behalf of the threads that wish to run them.
The two keys here are the widget.after method that "registers a callback function that will be called after a given number of milliseconds" and a Queue to put and get requests.
This way, a thread can just put the tuple (function, args, kwargs) into the queue instead of calling the function, resulting in a unpainful change of the original code.
This is the final, thread-safe version:
from Tkinter import *
import tkMessageBox
from time import sleep
import threading
from Queue import Queue
# Thread-safe version.
# Tkinter functions are put into queue and called by tkloop in the main thread.
q = Queue()
def button1():
sleep(2)
q.put(( tkMessageBox.showinfo, ('title', 'button 1'), {} ))
def button2():
sleep(2)
q.put(( tkMessageBox.showinfo, ('title', 'button 2'), {} ))
def start_thread(fun, a=(), k={}):
threading.Thread(target=fun, args=a, kwargs=k).start()
def tkloop():
try:
while True:
f, a, k = q.get_nowait()
f(*a, **k)
except:
pass
root.after(100, tkloop)
root = Tk()
frame = Frame(root)
frame.pack()
Frame(root).pack( side = BOTTOM )
Button(frame, command=lambda: start_thread(button1), text="Button 1").pack( side = LEFT)
Button(frame, command=lambda: start_thread(button2), text="Button 2").pack( side = LEFT )
tkloop() # tkloop is launched here
root.mainloop()
Edit: two-way communication: if your threads need to get informations from the main (e.g. return values from tkinter functions) you can edit the interface of tkloop adding a queue for the return values. Here's an example based on the code above:
def button1():
q1 = Queue()
sleep(2)
q.put(( tkMessageBox.askokcancel, ('title', 'question'), {}, q1 ))
response = 'user said ' + 'OK' if q1.get() else 'CANCEL'
q.put(( tkMessageBox.showinfo, ('title', response), {}, None ))
# ...
def tkloop():
try:
while True:
f, a, k, qr = q.get_nowait()
r = f(*a, **k)
if qr: qr.put(r)
except:
pass
root.after(100, tkloop)
tkinter is not thread safe -- you can't reliably call any tkinter functions from any thread other than the one in which you initialized tkinter.
If you want your other thread to block until you get response (e,g: you want to ask a question and wait for the answer) you can use this function:
def runInGuiThreadAndReturnValue(self, fun, *args, **kwargs):
def runInGui(fun, ret, args, kwargs):
ret.append(fun( *args, **kwargs))
ret = []
sleeptime = kwargs.pop('sleeptime', 0.5)
self.after(0, runInGui, fun, ret, args, kwargs)
while not ret:
time.sleep(sleeptime)
return ret[0]
Related
I don't know if what I'm seeking to achieve is even possible. I have written a tkinter app which imports a method from an external class. This method runs a hill-climbing algorithm which will run perpetually and try to improve upon the "score" that it has calculated. After each pass, it presents the current output and score to the user and asks (on the command line) if they wish to continue.
The first challenge in getting this working was to implement threading. I have this working, but I don't don't know if I have done it correctly.
The algorithm will continue until the user signals that they have got the answer they were looking for, or loses the will to live and presses CTRL-C.
In my tkinter main app, this presents me with two problems:
How to display the output from this external method. I tried to write a while loop that polled the output field periodically, (see commented out section of the "start_proc" method) but that was clearly never going to work, and besides, I would ideally like to see real-time output; and
How to interact with the algorithm to either continue or stop (see commented out section of the "my_long_procedure" method). As you can see, I can inject a "stopped" attribute, and that does indeed halt the algorithm, but I can't take my eyes off the output because the desired answer may have gone past before I can press stop.
Below is, I hope, a simplified bare-bones example of what I am trying to do.
This is a learning exercise for me and I would be grateful of any help.
import tkinter as tk
from threading import Thread
from random import randint
import time
class MyTestClass: # This would actually be imported from another module
def __init__(self):
self.stopped = False
def my_long_procedure(self):
# Fake method to simulate actual algorithm
count = 0
maxscore = 0
i = 0
while count < 1000 and not self.stopped:
i += 1
score = randint(1,10000)
if score > maxscore:
maxscore = score
self.message = f'This is iteration {i} and the best score is {maxscore}'
print(self.message)
# self.carry_on = input("Do you want to continue? ")
# if self.carry_on.upper() != "Y":
# return maxscore
time.sleep(2)
print('OK - You stopped me...')
class MyMainApp(tk.Tk):
def __init__(self, title="Sample App", *args, **kwargs):
super().__init__()
self.title(title)
self.test_run = MyTestClass()
self.frame1 = tk.LabelFrame(self, text="My Frame")
self.frame1.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky=tk.NSEW)
self.frame1.columnconfigure(0, weight=1)
self.frame1.rowconfigure(0, weight=1)
start_button = tk.Button(self.frame1, text="Start!",
command=self.start_proc).grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
stop_button = tk.Button(self.frame1, text="Stop!",
command=self.stop_proc).grid(row=0, column=2, padx=5, pady=5, sticky=tk.E)
self.output_box = tk.Text(self.frame1, width=60, height=8, wrap=tk.WORD)
self.output_box.grid(row=1, column=0, columnspan=3, sticky=tk.NSEW)
def start_proc(self):
self.test_run.stopped = False
self.control_thread = Thread(target=self.test_run.my_long_procedure, daemon=True)
self.control_thread.start()
time.sleep(1)
self.output_box.delete(0.0, tk.END)
self.output_box.insert(0.0, self.test_run.message)
# self.control_thread.join()
# while not self.test_run.stopped:
# self.output_box.delete(0.0, tk.END)
# self.output_box.insert(0.0, self.test_run.message)
# time.sleep(0.5)
def stop_proc(self):
self.test_run.stopped = True
if __name__ == "__main__":
MyMainApp("My Test App").mainloop()
If you own the implementation of the algorithm you could pass a callback (a method of MyMainApp) so that the algorithm signals on his own whenever he has done some "work" worth notification to the user. This would look like:
def my_long_procedure(self,progress):
The callback prototype could be:
def progress(self,iteration,result):
and instead of print(self.message) you could do progress(i,maxscore)
starting the thread:
self.control_thread = Thread(target=self.test_run.my_long_procedure, daemon=True,args=(self.progress,))
Unfortunatly you need to be aware that you cannot refresh the GUI from another thread than the main thread. This is a much discussed tkinter limitation. So in a nutshell you cannot directly call any of your GUI widgets from progress function. The workaround to this issue is to store the progress in the progress function and register a function to be executed whenever the tkinter main loop will be idle. you can do somethink like self.after_idle(self.update_ui) from progress method. update_ui() would be a new methode updating eg a progress bar or your graph using data transmitted by the progress callback and saved as MyMainApp properties.
More on this "pattern" (using message queues instead of callback) here:
https://www.oreilly.com/library/view/python-cookbook/0596001673/ch09s07.html
I am new to Python and failing on first hurdle. I have a message box with a canvas with image loaded into it that's triggered to activate via a Passive infrared sensor.
Got everything working but I want the message box to disappear after 5 secs. The time delay isn't a problem but trying to get the message box to go is another thing been using destroy() but nothing.
Understand there are window levels I.e Toplevel. But wondered if someone could point me in the right direction.
The small function below will do the job. By setting the type you can choose for: info, warning or error message box, the default is 'Info'. You can set also the timeout, the default is 2.5 seconds.
def showMessage(message, type='info', timeout=2500):
import tkinter as tk
from tkinter import messagebox as msgb
root = tk.Tk()
root.withdraw()
try:
root.after(timeout, root.destroy)
if type == 'info':
msgb.showinfo('Info', message, master=root)
elif type == 'warning':
msgb.showwarning('Warning', message, master=root)
elif type == 'error':
msgb.showerror('Error', message, master=root)
except:
pass
Call the function as follow:
For message type 'Info' and timeout of 2.5 seconds:
showMessage('Your message')
Or by your own settings for type message 'Error' and timeout 4 seconds:
showMessage('Your message', type='error', timeout=4000)
Without any code to go off of this is the best that I can give you.
This is an object-oriented program and truth be told I just started understanding what they are doing.
However just focus on def create_window(self)
It takes the window instance t which it declared and placed all the info on and .destroy it.
import tkinter as tk
class MainWindow(tk.Frame):
counter = 0
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.button = tk.Button(self, text="Create new window", command=self.create_window)
self.button.pack(side="top")
def create_window(self):
self.counter += 1
t = tk.Toplevel(self)
t.wm_title("Window #%s" % self.counter)
l = tk.Label(t, text="This is window #%s" % self.counter)
l.pack()
b = tk.Button(t, text='close', command=t.destroy)
b.pack()
if __name__ == "__main__":
root = tk.Tk()
main = MainWindow(root)
main.pack(side="top", fill="both", expand=True)
root.mainloop()
Hope this helps!
I have a minimal tkinter program which analyses some data. Some of the datafiles are quite large, so to ensure that the GUI remains responsive I load the data in a new thread.
How can I run analysis on the data once the thread has terminated?
Some example code is below.
import tkinter
from threading import Thread
from time import sleep
result = []
def func(result):
sleep(10)
ans = 1
result.append(ans)
class myApp(tkinter.Tk):
def __init__(self, parent):
tkinter.Tk.__init__(self, parent)
self.grid()
self.myButton = tkinter.Button(self, text="Press me!", command=self.onButtonPress)
self.myButton.grid(column=0, row=0)
def onButtonPress(self):
thread = Thread(target=func, args=(result,))
thread.start()
self.myButton["text"]=result
app = myApp(None)
app.mainloop()
How can I make the button text change only when func returns?
multiprocessing has an isalive() function and so you can use Tkinter's after() to query it periodically if you want to switch over
I'm realtively new to python and am making a GUI app that does a lot of file i/o and processing. To complete this i would like to get a confirmation box to pop-up when the user commits and actions. From this when clicking 'yes' the app then runs the i/o and displays a progress bar.
From other threads on here I have gotten as far as reading about the requirement to create an addtional thread to take on one of these processes (for example Tkinter: ProgressBar with indeterminate duration and Python Tkinter indeterminate progress bar not running have been very helpful).
However, I'm getting a little lost because I'm not activating the threaded process from the Main() function. So I'm still getting lost in how, and where, I should be creating the progress bar and passing of the i/o process to another thread (reading in a csv file here).
Here is my code and I would be very grateful for any help anyone can give me:
import tkinter as tk
import tkinter.messagebox as messagebox
import csv
import tkinter.ttk as ttk
import threading
class ReadIn(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Read in file and display progress")
self.pack(fill=tk.BOTH, expand=True)
self.TestBtn = tk.Button(self.parent, text="Do Something", command=lambda: self.confirm_pb())
self.TestBtn.pack()
def confirm_pb(self):
result = messagebox.askyesno("Confirm Action", "Are you sure you want to?")
if result:
self.handle_stuff()
def handle_stuff(self):
nf = threading.Thread(target=self.import_csv)
nf.start()
self.Pbar()
nf.join()
def Pbar(self):
self.popup = tk.Tk()
self.popup.title('Loading file')
self.label = tk.Label(self.popup, text="Please wait until the file is created")
self.progressbar = ttk.Progressbar(self.popup, orient=tk.HORIZONTAL, length=200,
mode='indeterminate')
self.progressbar.pack(padx=10, pady=10)
self.label.pack()
self.progressbar.start(50)
def import_csv(self):
print("Opening File")
with open('csv.csv', newline='') as inp_csv:
reader = csv.reader(inp_csv)
for i, row in enumerate(reader):
# write something to check it reading
print("Reading Row " + str(i))
def main():
root = tk.Tk() # create a Tk root window
App = ReadIn(root)
root.geometry('400x300+760+450')
App.mainloop() # starts the mainloop
if __name__ == '__main__':
main()
The statement nf.join() in function handle_stuff() will block tkinter's main loop to show the progress bar window. Try modify handle_stuff() as below:
def handle_stuff(self):
nf = threading.Thread(target=self.import_csv)
nf.start()
self.Pbar()
#nf.join() # don't call join() as it will block tkinter's mainloop()
while nf.is_alive():
self.update() # update the progress bar window
self.popup.destroy()
I create a simple window with tkinter and start it as thread to keep the main program running next to the window. This is a shortened version of it:
import tkinter as tk
import threading
class mainWindow(threading.Thread):
def __init__(self, winWidth=500, winHeight=300):
threading.Thread.__init__(self)
self.winWidth = winWidth
self.winHeight = winHeight
# Save all drawn objects, to move or delete them later
self.bricks = []
self.start() #start thread
def run(self):
# parent object for all windows
self.master = tk.Tk()
self.master.protocol("WM_DELETE_WINDOW", self.callback)
self.show()
def callback(self):
self.master.quit()
# Initialize everything important
def show(self, tileSize=10):
# create main window
self.w = tk.Canvas(
self.master,
width=self.winWidth,
height=self.winHeight,
background="white")
self.w.pack()
# draw brick
color = "gray49"
posX = 200
posY = 100
self.bricks.append(self.w.create_rectangle(posX, posY, posX+20, posY+20, fill=color))
tk.mainloop()
def move_brick(self, x,y):
self.w.move(self.brick, x, y)
mainWindow = mainWindow()
mainWindow.move_brick(100,100)
When I run the shown code the window opens correctly, but when I try to move the rectangle with move_brick(...) I get this error:
AttributeError: 'mainWindow' object has no attribute 'w'
Why can't the object find my Canvas w?
You probably have a race condition, which is common for threaded applications. The main thread is likely calling move_brick before the worker thread has a chance to create the widget.
You can probably see this happening if you add print statements right before and after creating the widget, and in your move_brick function.
Even if you fix this, this code likely won't work because all tkinter code needs to run in a single thread. Creating the GUI in one thread and calling move_brick in another thread is not the right way to use tkinter.