Call back in Python - python

Could some explain how call back methods work, and if possible, give me an example in Python? So as far as I understand them, they are methods which are provided by the user of an API, to the API, so that the user doesn't have to wait till that particular API function completes. So does the user program continue executing, and once the callback method is called by the API, return to point in the program where the callback method was provided? How does a callback method essentially affect the 'flow' of a program?
Sorry if I'm being vague here.

Callbacks are just user-supplied hooks. They allow you to specify what function to call in case of certain events. re.sub has a callback, but it sounds like you are dealing with a GUI, so I'll give a GUI example:
Here is a very simple example of a callback:
from Tkinter import *
master = Tk()
def my_callback():
print('Running my_callback')
b = Button(master, text="OK", command=my_callback)
b.pack()
mainloop()
When you press the OK button, the program prints "Running my_callback".
If you play around with this code:
from Tkinter import *
import time
master = Tk()
def my_callback():
print('Starting my_callback')
time.sleep(5)
print('Ending my_callback')
def my_callback2():
print('Starting my_callback2')
time.sleep(5)
print('Ending my_callback2')
b = Button(master, text="OK", command=my_callback)
b.pack()
b = Button(master, text="OK2", command=my_callback2)
b.pack()
mainloop()
you'll see that pressing either button blocks the GUI from responding until the callback finishes. So the "user does have to wait till that particular API function completes".

Related

Alternative To Time.Sleep() for pausing a function

I created an example code because my original is too big and has private information(My own) in it.
While running a program from a Tkinter GUI, it runs the program but makes the GUI unresponsive because of time.sleep() blocking the GUI from updating.
I am trying to avoid using timers because it fires a different function after a duration instead of simply pausing the function and then continuing the same function.
Is there an alternative that does not block the GUI but still adds a delay inside of the function?
Example Code:
from tkinter import *
import time
wn = Tk()
wn.geometry("400x300")
MyLabel = Label(wn, text="This is a Status Bar")
MyLabel.pack()
def MyFunction():
Value = 1
while Value < 10:
print("Do something")
time.sleep(1) **# - here blocks everything outside of the function**
MyLabel.config(text=Value)
# A lot more code is under here so I cannot use a timer that fires a new function
Value = 1
MyButton = Button(wn, text="Run Program", command=MyFunction)
MyButton.pack()
wn.mainloop()
Edit: Thanks so much, you're answers were fast and helpful, I changed the code and added "wn.mainloop()" after the delay and replaced "time.sleep(1)" with wn.after(100, wn.after(10, MyLabel.config(text=Value))
here is the final code:
from tkinter import *
import time
wn = Tk()
wn.geometry("400x300")
MyLabel = Label(wn, text="This is a Status Bar")
MyLabel.pack()
def MyFunction():
Value = 0
while Value < 10:
print("Do something")
wn.after(10, MyLabel.config(text=Value))
Value += 1
wn.mainloop()
MyButton = Button(wn, text="Run Program", command=MyFunction)
MyButton.pack()
wn.mainloop()
The short answer is that you can use wn.after() to request a callback after a certain amount of time. That's how you handle it. You get a timer tick at a one-per-second rate, and you have enough state information to let you proceed to the next state, then you go back to the main loop.
Put another way, timers are exactly how you have to solve this problem.
Fundamentally, any callback function in Tkinter runs in the main GUI thread, and so the GUI thread will block until the function exits. Thus you cannot add a delay inside the function without causing the GUI thread to be delayed.
There are two ways to solve this. One would be to refactor your function into multiple pieces so that it can schedule the remaining work (in a separate function) via .after. This has the advantage of ensuring that all of your functions are running in the main thread, so you can perform GUI operations directly.
The other way is to run your function in a separate thread that is kicked off whenever your main callback is executed. This lets you keep all the logic inside the one function, but it can no longer perform GUI operations directly - instead, any GUI operations would have to go through an event queue that you manage from the main thread.
You can combine after() and wait_variable() to simulate time.sleep() without blocking tkinter from handling pending events and updates:
def tk_sleep(delay):
v = wn.IntVar()
# update variable "delay" ms later
wn.after(delay, v.set, 0)
# wait for update of variable
wn.wait_variable(v)
Using tk_sleep() in your while loop:
def MyFunction():
Value = 1
while Value < 10:
print("Do something")
tk_sleep(1000) # waits for one second
MyLabel.config(text=Value)
# A lot more code is under here so I cannot use a timer that fires a new function
Value += 1

How to run multiple threads in tkinter

Let's say if we write something like this:
import threading
Button(root, command=threading.Thread(target=func1).start)
Now if we click the button once then it will be fine but we try to click the button again then a error comes "Thread can only be executed once".
So, how to avoid this
Edited Answer::
As you clarified it in comments, you can redefine the Button every time it is clicked allowing it to accept multiple clicks and thus creating multiple threads as required.
You could do that inside the target func1() or callback function for threading.Thread object.
A working example example would be like this:
import tkinter as tk
import threading
def func1():
theButton.configure(command=threading.Thread(target=func1).start)
print('Do everything else here')
root = tk.Tk()
theButton = tk.Button(root, text='Start', command=threading.Thread(target=func1).start)
theButton.pack()
root.mainloop()
Edit: Thanks to CoolCloud for suggesting a better way to configure the Button inside callback func1().
It is because you create one instance of threading.Thread() and pass its start method to command option of the button. You should create new instance whenever the button is clicked by using lambda:
import tkinter as tk
import threading
root = tk.Tk()
def func1():
print('Hello')
tk.Button(root, text='Go', command=lambda: threading.Thread(target=func1).start()).pack()
root.mainloop()

Unable to make a button (created using tkinter) quit automatically

I've written a script using tkinker in python. When I run the script, it receives an input and print that to the console. It is working great.
What I wish to do is add any functionality to my existing script in such a way so that after filling in the inputbox when I press the get button, it will print the value in the console and quits automatically. Once again, my existing script is capable of printing values. I need to make that button quit as soon as the printing is done. Any help on this will be highly appreciated.
Here is what I've tried so far:
from tkinter import *
master = Tk()
e = Entry(master)
e.pack()
e.focus_set()
callback = lambda : get_val(e.get())
get_val = lambda item: print(item) #this extra function is for further usage
Button(master, text="get", width=10, command=callback).pack()
master.mainloop()
This is how the inputbox looks like:
Modify the callback function as:
def callback():
get_val(e.get()) #Gets your stuff done
master.destroy() #Breaks the TK() main loop
exit() #Exits the python console
Here,master.destroy() breaks the master.mainloop() loop and thus terminates the GUI and finally exit() makes it exit the python console.
Maintaining your lambda syntax:
callback = lambda : (print(e.get()), master.destroy())
The key is to call master.destroy().

press more than one Tkinter button

Hi i was wondering if you could make it so you could have more that one button pressed at a time?
Like:
from Tkinter import *
tkwin = Tk()
def delayedDoSomethings():
for i in range(1,10000000):
print 'hi',i
def delayedDoSomething():
for i in range(1,10000000):
print i
a = Button(tkwin, text="Go", command=delayedDoSomething)
a.pack()
b = Button(tkwin, text="Go hi", command=delayedDoSomethings)
b.pack()
tkwin.mainloop()
and the i would be able to click "go" and then "go hi" but i cant because the window freezes until it is done. does any one know how to make it so that you can press more that one button at a time?
What you want here is to use threads. Threads allow you to have multiple pieces of code executing at the same time (or they will at least appear to be executing simultaneously)
Inside of delayedDoSomethings(), you'll want to spawn a new thread that does the actual work, so that you can return control to Tkinter in the main thread.
You would do the same thing in delayedDoSomething().
Here's some actual code that you could use in delayedDoSomethings()
def delayedDoSomethings():
def work():
for i in rance(1, 10000000):
print 'hi',i
import thread
thread.start_new_thread(separateThread, ()) #run the work function in a separate thread.
Here is the documentation for Python's built-in thread module, which will be useful.

Intercept Tkinter "Exit" command?

I'm writing a client-server program in Python with Tkinter. I need the server to keep track of the connected clients. For this, I would like to have the client send an automated message to the server after the exit button(the standard "X" in the corner) is clicked. How can I know when the user is exiting the program?
You want to use the wm_protocol method of the toplevel window. Specifically, you are interested in the WM_DELETE_WINDOW protocol. If you use that method, it allows you to register a callback which is called when the window is being destroyed.
Usage:
root.protocol("WM_DELETE_WINDOW", app.on_delete)
You can use python atexit module.
For example:
import atexit
def doSomethingOnExit():
pass
atexit.register(doSomethingOnExit)
In my case, the following code didn't work:
root.protocol("WM_DELETE_WINDOW", app.on_delete) # doesn't work
However, it worked using this form:
root.wm_protocol ("WM_DELETE_WINDOW", app.on_delete) # does work
FWIW: It is also possible to assign a widget-specific behavior.
If you want an action to occur when a specific widget is destroyed, you may consider overriding the destroy() method. See the following example:
class MyButton(Tkinter.Button):
def destroy(self):
print "Yo!"
Tkinter.Button.destroy(self)
root = Tkinter.Tk()
f = Tkinter.Frame(root)
b1 = MyButton(f, text="Do nothing")
b1.pack()
f.pack()
b2 = Tkinter.Button(root, text="f.destroy", command=f.destroy)
b2.pack()
root.mainloop()
When the button 'b2' is pressed, the frame 'f' is destroyed, with the child 'b1' and "Yo!" is printed.
I posted the same answer on this topic.
Don't forget to use a lambda like this:
class App:
def run(self):
self.root.protocol("WM_DELETE_WINDOW", lambda: self.quit())
def quit(self):
self.root.destroy()

Categories