Reputation: 669
Short version: Is there a way to control the order in which callbacks associated with different widgets are handled in Tkinter?
Longer version: I have been using the following program in IDLE while testing out and learning Tkinter:
import Tkinter
guiRoot = Tkinter.Tk()
hWindow = Tkinter.Frame(guiRoot)
hWindow.grid(); x = 0; y = 0
et1 = Tkinter.Entry(hWindow)
et2 = Tkinter.Entry(hWindow)
def ut(x, y, event):
print "X",x,", Y:",y
print "Args:",
print "Widget:",event.widget.get()
print
def Tkquit():
print "Leaving program..."
et1 = Tkinter.Entry(hWindow)
et2 = Tkinter.Entry(hWindow)
eb = Tkinter.Button(hWindow, command=Tkquit, text="Send")
et1.grid(column=x, row=y)
et1.bind("<FocusOut>", lambda event, x1=x, y1=y:ut(x1, y1, event))
y = y + 1; et2.grid(column=x, row=y)
et2.bind("<FocusOut>", lambda event, x1=x, y1=y:ut(x1, y1, event))
y = y + 1
eb.grid(column=x, row=y)
guiRoot.mainloop()
When I move from one Entry field to the other, ut() is called. When I click on the Button, I get the message "Leaving program..." (which will later have quit code in the routine), but there is no message from the text widget that just lost focus.
This leads me to two questions:
1) Why isn't the callback for the Entry field being called?
And, also, implied, if I want that button to quit the application, is that I want other callbacks to be completed before the one from that button. So:
2) How can I control the order in which callbacks are called by Tkinter?
Upvotes: 3
Views: 1953
Reputation: 1
Yes, it is.
Use a uniform <callbackHANDLER> process, to be registered at the Tkinter's widget's public Event-routing interface ( the caller-side(s) ( ..., command =
<callbackHANDLER> ), <aTkWidgetINSTANCE>.bind(
<<_anEventTYPE_>>,
<callbackHANDLER> )
or <aTkStateVARIABLE>.trace_variable(
{ "w" | "r" | "u" }, <callbackHANDLER> )
).
Plus design an appropriate middle-layer application logic, to receive all Events-> routed therein, snap-a-caller's-context & stack/re-order/process the stream of incoming callback requests in the order in which you want their processing to appear.
Tkinter has a lot of details associated in instance variables for each Event, so you have a full control, per call / per incident.
Edit#1
2014-08-25 07:00
A command
setting has quite a powerful possibility to use lambda-containers with optional instance-wrapping:
command = ( lambda deltaMinutes = 1, # sets lambda-container to use a var.
aWidget = self.B1, # sets lambda-container to ref aWidget->B1
anotherVar = 0:
self.DateTimeNOW_MODEL_Add( deltaMinutes )
or # trick to work with a sequence of commands
self.LogPROCESS( "Step back 1x M1", anotherVar )
)
Upvotes: 0
Reputation: 385970
The reason your <FocusOut>
event doesn't get called when you press exit is because the entry widget doesn't lose focus. By default, Tkinter buttons don't get focus when you click on them. However, if you use a ttk.Button
, it does steal focus, so your callback will be called. Personally I think that's a bug in the ttk implementation of buttons, but it's been that way for years.
In either case, you can add bindings to buttons which will cause them to steal focus when you click on them. Doing so will cause any <FocusOut>
events to fire on the widget that previously had focus. Normally this isn't desired but Tkinter gives you that flexibility.
For example, add this to your code to get the <FocusOut>
event to fire when you click the button:
# force the button to steal focus when clicked
eb.bind("<1>", lambda event: eb.focus_set())
You can't control the order in which events are processed, because events are processed in the order that they occur. For a toolkit to do anything else would be wrong. I think you'll find that once you have a better understanding of how Tkinter works, you won't need to process events out of order.
Upvotes: 1
Reputation: 8956
I think you're solving the wrong problem here. When you use a library that has callbacks, you are the complete mercy of the library. Unless Tkinter provides you with the option to change this, you don't get to choose when your functions get called (if ever), or the order in which they are called. Chances are that they're heavily coupled with the internal implementation of Tkinter (i.e. "hard-wired") so they can't be changed easily.
If your program inherently depends on the events to fire in a certain order, then you have introduced a source of fragility to your program. In some rare cases, this may be necessary (e.g. for efficiency reasons, perhaps), but in most cases it simply adds unnecessary coupling of your program to the library. What if the library decides to change the order in which events are fired later on? What if the user finds a combination of events that causes your logic to break? What if you add something later on and introduce a bug in the logic?
To make your interface and logic robust, try to design your program in such a way that the order in which your handlers are called doesn't make any difference to the program behavior. Where possible, try to keep your functions idempotent to avoid a combinatoric explosion of states in your program.
In your case, if you want a certain action to be executed before the button is triggered, consider calling the said action to ensure that it does happen, regardless of whether Tkinter emits the FocusOut event.
Upvotes: 0