Basic Query Regarding Bindtags in Tkinter

Basic query regarding bindtags in tkinter

When you do a binding on a widget, you aren't actually binding to a widget per se. When you do mywidget.bind(...), what is actually happening is that the binding is associated with a bind tag with the same name as the widget.

When an event is detected, Tkinter first figures out which widget intercepted the event. This widget will have a list of zero or more (by default: four) bind tags associated with it. Tkinter will check each tag in order to see if there's a binding that matches the event. If it finds one, it will execute the binding and then continue to the next tag, until it runs out of tags or one of the bound functions returns the string "break".

The sequence looks something like this:

  1. You press the "x" key. At this point the "x" hasn't been inserted anywhere
  2. Tkinter gets the bind tags for the widget that has the keyboard focus.
  3. By default the first bind tag is the widget itself. Is there a binding on that tag? If so, execute it. For example, you might print the contents of the widget. Because no other bindings have fired yet, the "x" will not be part of the contents.
  4. If the bound function returns "break" then no more event processing is done. The "x" will not get inserted into the widget.
  5. If the widget did not return "break", tkinter proceeds to the next bind tag.
  6. By default, the next bind tag is the widget class. Is there a binding on the class that matches this event (a keypress of the letter "x")?
  7. In this case the class does have a binding for this event, which is to actually insert the "x" into the widget. Until this point, the "x" still hasn't been inserted into the widget. After this binding, however, if you did a print of the contents you would see the "x"
  8. Processing continues in this manner until all bind tags have been processed.

Based on the ongoing discussion in the comment section, it appears this is still unclear. I'll try to make this as simple as possible:

It is the class binding which copies a character from the in-memory event object to the widget and thus causing it to appear on screen. Before the class binding fires, the character will not appear in the widget. After the class binding it will be in the widget.

for what all stands in bindtags in tkinter?

It stands for "all widgets". It's just a string that all widgets by default have as part of their binding tags. It is useful when you want to create a binding that will be in effect for all widgets in the application as a whole.

When you call bind, you aren't actually binding to the widget per se. You're binding to a tag that corresponds to the widget. For example, if you create an Entry widget, the binding tags for that widget might look like ('.!entry', 'Entry', '.', 'all').

Note: if you want to see the binding tags for any widget, print the results of the bindtags method (eg: print(my_widget.bindtags()))

The first element in the list is the widget itself, represented as the internal name of the widget. It is followed by the widget class, the window that the widget is in (in this case, '.' which stands for the root window), and then the special tag "all". It is possible for you to add your own tags, but there's rarely a need to do so.

When an event occurs in a widget, tkinter will iterate over the binding tags looking for a binding. For example, if you click on a button it will see if you have a binding on the widget itself. It will then check to see if there is a binding for the Button class. It will next check to see if there are any bindings on the window as a whole. Finally, it will check to see if there are bindings on the "all" tag.

In this way it's possible to add a binding that works for the whole application, for every widget within a window, for ever widget of a particular class, or for a specific widget. You can also add your own tags, or remove the default tags. For example, to remove all built-in bindings for an Entry widget, you can remove the window class from the widgets binding tags. It will then have no default bindings.

Why binding a key to Entry widget doesn't work?

You don't have to manually build a logic for it, tkinter luckily already provides it. You just have to set an extra option to the entry widget, show='*':

e = Entry(window, borderwidth=3, width=50, bg="white", fg="black", show='*')

There are many reasons as to why you should avoid building/using this logic of your own, to begin with, once you do get(), you will only get **** and not whatever the text originally was.

How to use default bindtag behavior as methods?

To invoke the callback bound to a given sequence (e.g. <Button-1>), you can do widget.event_generate(<sequence>, **kwargs). The optional keyword arguments can be x=0, y=0 for a <Button-1> event for instance. This will trigger all callbacks associated with the sequence (it does not matter whether they were bound with bind, bind_class or bind_all).

In the below example, when the second button is clicked, it lowers the first one as if it were clicked too:

import tkinter as tk

root = tk.Tk()

first_btn = tk.Button(root, text="1st")
second_btn = tk.Button(root, text="2nd", command=lambda: first_btn.event_generate('<Button-1>'))

first_btn.pack()
second_btn.pack()

root.mainloop()

However, when doing tests interactively from the command line, generating keyboard events often does not work because the widget does not have keyboard focus. So, with the following code, when clicking on the button, the View menu will open as if we had done Alt+v:

import tkinter as tk

root = tk.Tk()
menubar = tk.Menu(root)
viewMenu = tk.Menu(menubar, tearoff=0)
viewMenu.add_command(label="Item 1")
viewMenu.add_command(label="Item 2")
menubar.add_cascade(menu=viewMenu, label="View", underline=0)
root.config(menu=menubar)
tk.Button(root, text='Test', command=lambda: root.event_generate('<Alt-v>')).pack()
root.mainloop()

But when I do root.event_generate('<Alt-v>') from the IPython QtConsole, nothing happens. The workaround is to force keyboard focus before generating the event:

root.focus_force()
root.event_generate('<Alt-v>')

Different results on different ways of binding?

You cannot use all three bindings and get the same result1 because the result is dependent on the ordering of the widget bind tags.

Every widget has an ordered set of bind tags. Bindings are actually attached to these tags rather than the actual widget. It just so happens that the first bind tag is the name of the widget.

By default a widget has the following bind tags in the following order:

  1. the widget itself
  2. the widget class (internal tk class, not tkinter class)
  3. the widget toplevel (or root window)
  4. "all"

When an event occurs on a widget, the following four things happen:

  1. if the widget has a binding that matches the event, process that binding
  2. if the widget class has a binding that matches the event, process that binding
  3. if the widget's toplevel (or root) has a binding that matches the event, process that binding
  4. if there is a binding on "all", process that event.

At any time, if a binding returns the literal string "break", no more bindings will be processed. Without returning "break", all four of the event handlers are processed. In the case of your widget binding returning "break", the class, toplevel and "all" bindings won't be processed.

In the specific case of binding <Key> with bind_all, this is what happens:

  1. the widget has no binding that matches the event, so nothing is done
  2. the widget class has a binding, so it is processed. In this case a letter for the key is inserted into the widget
  3. the widget's toplevel/root has no binding, so nothing is done
  4. the "all" tag has a binding so it will insert "a\n".

Because the binding for "all" happens after the binding for the widget class, returning "break" has no effect, and cannot prevent the default behavior of inserting an "a" in addition to your custom behavior.

Bind tags are a stunningly beautiful way of handling events. I've used at least half a dozen different GUI toolkits in several different languages over the course of many years, and nothing comes close to the power of tk's bind tags mechanism.


1 You actually can get the same behavior for all three by adding or removing bind tags, or changing the order of the bind tags. You do this with the bindtag method. There is rarely a need to do so, but it is remarkably handy for some special cases.

is there a way to bind key press to root without disturbing default binds in tkinter

The problem isn't because you're replacing the default bindings. That's simply not how bindings work in tkinter. There are no bindings directly tied to the root widget or any other specific widget. Default bindings are implemented as bindings on "all" or on widget classes, not individual widgets.

They work fine if i bind it to the text widget but the last entered letter doesnt get highlighted.

That is because a binding on a widget happens before the default bindings. So, if you type "a", your code will be called before the code that inserts the letter "a".

There is a question on this site related to the order in which events are processed. While the answer is tied to an Entry widget, the exact same concept applies to all widgets. See this answer to the question Basic query regarding bindtags in tkinter

How to bind multiple widgets with one bind in Tkinter?

for b in [B1, B2, B3]:
b.bind("<Enter>", SetColor)
b.bind("<Leave>", ReturnColor)

You could go further and abstract all of your snippet:

for s in ["button 1", "button 2", "button 3"]:
b=Button(root, text=s, bg="white")
b.pack()
b.bind("<Enter>", SetColor)
b.bind("<Leave>", ReturnColor)

Now it's easy to add extra buttons (just add another entry to the input list). It's also easy to change what you do to all the buttons by changing the body of the for loop.

Remove tkinter text default binding

You can overwrite a binding by having your function return the string "break". For example:

def Ilim(self): # Prints How many lines are in
info = int(tbox.index('end-1c').split('.')[0])
print(info)
return "break"

If you want to completely remove all bindings (including the bindings that allow you to insert characters), that can be easily accomplished. All of the bindings are associated with a "bind tag" (or "bindtag"). If you remove the bindtag, you remove the bindings.

For example, this removes all of the default bindings:

    bindtags = list(tbox.bindtags())
bindtags.remove("Text")
tbox.bindtags(tuple(bindtags))

For more information on bind tags, see this answer: https://stackoverflow.com/a/11542200/7432

Python Tkinter on <key>-Event entry.get() is lagging behind

This is not a bug: The event is triggered before the new character is inserted in the entry field, which is why the entry content is "lagging behind".

To fix this, bind instead to <KeyRelease> which is triggered after the character is inserted in the entry field.



Related Topics



Leave a reply



Submit