Python tkinter override default ctrl-h binding - python

I have no problem with binding ctrl-h. However, when I do the ctrl-h, I notice that the last character is also removed from the entry. I think this might be the default ctrl-h binding of python. How can I disable that?
---Update---
I have added the return 'break' thing. But it still doesn't work. The last character is immediately removed before the askstring dialog shows up. Here is the code that is bound.
def replace(self):
target = simpledialog.askstring(title = 'Replace', prompt = 'Replace Target')
if not target:
return 'break'
withValue = simpledialog.askstring(title = 'Replace', prompt = 'Replace With')
if not withValue:
return 'break'
for entry in self.entries.values():
setEntry(entry, entry.get().replace(target, withValue))
return 'break'
By the way I bind it with the master not the entry because I have a lot of entries. Binding with the master is way easier.
This is how I bind it.
self.master.bind('<Control-h>', lambda event: self.replace())
self.master is defined here:
class Generator(Frame):
def __init__(self, master):
Frame.__init__(self, master)
## init variables
self.master = master
This is what I pass in.
root = Tk()
gui = Generator(root)
gui.pack()
root.title('Generator')
root.mainloop()

Because you are binding to the root window rather than an individual widget, there's nothing you can do in your function. Bindings are processed in this order:
bindings on a specific widget
bindings on a widget class
bindings on the toplevel window in which the widget exists
bindings on the special tag "all"
If at any time in the processing of those bindings a function returns the string "break", no further processing will happen. Thus, if you have a binding on a specific widget and return "break", the default class binding won't be processed. However, if you return "break" from a binding to the root window, that binding isn't processed until after the class binding. Therefore, it's impossible for this sort of binding to prevent the default behavior.
However, tkinter bindings are remarkably customizable, so there are solutions. Given that you potentially want to inhibit the default behavior, the most straight-forward solution is to either bind to the class so that you completely replace the default behavior, or bind to each widget individually so that you can prevent the class binding from happening.
If you really want the binding to be universal by binding to the root window, then the easiest solution might be to change the order of processing for all widgets that have default bindings for control-h.
For example, to move the handling of root-level bindings before class-level bindings, you can do something like this:
entry = tk.Entry(root)
bindtags = entry.bindtags()
entry.bindtags((bindtags[2], bindtags[0], bindtags[1], bindtags[3]))
For more information on exactly how bindings are processed, you might want to look at the following questions:
Basic query regarding bindtags in tkinter
How to bind self events in Tkinter Text widget after it will binded by Text widget?
Here is a simple example that binds to the root window, but changes the bind tags so that the default binding can be defeated by returning "break":
import tkinter as tk
def custom_backspace(event):
entry.insert("insert", "<backspace>")
return "break"
root = tk.Tk()
entry = tk.Entry(root)
entry.pack(fill="x")
bindtags = entry.bindtags()
entry.bindtags((bindtags[2], bindtags[0], bindtags[1], bindtags[3]))
root.bind("<Control-h>", custom_backspace)
root.mainloop()

Related

tkinter: Unbind not working, even workarounds don't work

I bring up here a problem, that's been there for ages, but is obviously still not solved and older workarounds don't work on my Python 3.7.2 (64-bit on Win10).
I have this code:
import tkinter as tk
import tkinter.simpledialog
# message box to enter a value where to set the scale to
class EnterValueBox(tk.simpledialog.Dialog):
def body(self, master):
self.e = tk.Entry(self, width=10)
self.e.pack(pady=5)
return self.e # initial focus
def apply(self):
print(self.e.get())
# callback to open message box
def enterValue(event):
EnterValueBox(root, title="Enter Value 0..100")
# create window with scale widget
root = tk.Tk()
scale = tk.Scale(root, orient=tk.HORIZONTAL, from_=0, to=100)
scale.pack()
# unbind any button-3 events
scale.unbind("<ButtonPress-3>")
scale.unbind("<ButtonRelease-3>")
scale.unbind("<Button-3>")
# bind button-3 press event to open message box
scale.bind("<ButtonPress-3>", enterValue)
tk.mainloop()
It creates a window with a single scale widget. I want to bind ButtonPress-3 to open a little dialog to directly enter a new value. The code only prints that value to the shell, but the example shows, that the unbind is not working, because after printing the value, the dialog box is closed (when the user clicks OK) and then the default binding is executed, which sets the slider, where the user clicked in the trough of the slider widget.
I tried the workaround from Deleting and changing a tkinter event binding with a PatchedScale widget (instead of the PatchedCanvas shown there), but that didn't make any difference.
Any help would be greatly appreciated!
The default bindings are not on the widget, they are on the widget class. Calling unbind on a widget for which there is no widget-specific binding won't have any effect.
If you don't want the default binding to run after your widget-specific binding, the normal technique is to have your bound function return the string break.
def enterValue(event):
EnterValueBox(root, title="Enter Value 0..100")
return "break"

Python tkinter When Toplevel object is destroyed, With toplevel binding for <Destroy> the bound command executes multiple times

In the following code, When the toplevel window is destroyed, the command in the
bind statement is executed multiple times. Probably once for each child widget within the Top Level. When I change the toplevel to a Frame, the bind command only executes once.
In the example, quit() or raise SystemExit are deferred until the command finishes its looping. Why is this happening?
import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showinfo
class PlainFrame(tk.Frame):
def __init__(self,parent):
super().__init__(parent)
self.butts = []
for i in range(20) :
# button
self.butts.append(ttk.Button(self, text=f'Click Me {i}'))
self.butts[i]['command'] = self.button_clicked
self.butts[i].grid(column=0,row=i)
self.pack()
def button_clicked(self):
showinfo(title='Information',
message='Hello, Tkinter!')
class MainFrame(tk.Toplevel):
#class MainFrame(tk.Frame):
def __init__(self, container,*args,**kwargs):
super().__init__(container,kwargs)
options = {'padx': 5, 'pady': 5}
# label
self.label = ttk.Label(self, text='Hello, Tkinter!')
self.label.pack(**options)
self.quit_button = ttk.Button(self,text='Quit',
command = self._quit)
self.quit_button.pack()
self.frame = PlainFrame(self)
# add when frame
#self.pack()
def button_clicked(self):
showinfo(title='Information',
message='Hello, Tkinter!')
def _quit(self):
self.destroy()
class App(tk.Tk):
def __init__(self):
super().__init__()
# configure the root window
self.title('My Awesome App')
self.geometry('600x100')
def quitting(self,event):
print ('just passing through')
quit()
raise SystemExit
if __name__ == "__main__":
app = App()
frame = MainFrame(app)
# app.withdraw()
frame.bind('<Destroy>',app.quitting)
app.mainloop()
With toplevel binding for the bound command executes multiple times
Yes, this is how tkinter is designed to work.
When you bind to something, you don't bind to a widget. Rather, you bind to a binding tag. Every widget has a set of binding tags. When a widget receives an event, tkinter will check each of its binding tags to see if there's a function bound to it for the given event.
So, what are the widget binding tags? Every widget gets the binding tag "all". Each widget also gets a binding tag named after the widget itself. It gets a third binding tag that is the name of the widget class (eg: "Button", "Label", etc). The fourth tag -- the one causing you trouble -- is the name of the window that contains the widget. The order goes from most to least specific: widget, widget class, window, "all".
You can see this by printing out the binding tags for a widget. Consider the following code:
import tkinter as tk
root = tk.Tk()
toplevel = tk.Toplevel(root)
label = tk.Label(toplevel)
print(f"binding tags for label: {label.bindtags()}")
When run, the above code produces this output:
binding tags for label: ('.!toplevel.!label', 'Label', '.!toplevel', 'all')
The first string, .!toplevel.!label is the internal name of the label widget. Label is the widget class, .!toplevel is the name of the toplevel widget, and then there's the string all.
What happens when you click on the label? First, tkinter will check to see if there is a binding for the button click on the tag .!toplevel.!label. It will have one if you bound to the widget itself. Next, it will check to see if there is a binding on the widget class. Widgets like scrollbars and buttons will have a binding on the widget class, but label won't. Next, tkinter will see if there's a binding on the window itself for the event. And finally, it will see if there is a binding on the special tag all.
You can alter the bindtags for a widget by passing the list of binding tags to the bindtags command. For example, if you want every widget to have a bind tag for the frame, you could set the bindtags to include the frame, and then every widget will respond to an event that is bound to the frame.
You can use the same technique to remove bindings as well. For example, if you wanted to remove all default bindings from a Text widget, you could remove Text from its list of bind tags. Once you do that, the widget will not respond to any key presses or key releases.
The canonical description of how binding tags works is in the bindtags man page for tcl/tk.

Tk(), Toplevel() and winfo_toplevel(). Difference between them and how and when to use effectively?

I am trying to understand how a widget is being created. And I found above three functions are getting used in creating a widget, yet I couldn't come up with the difference and the advantage of one over the other. Even though, I had taken a look on this answer that still leaves me with confusion (and it hadn't said anything about winfo_toplevel too).
Here is my code.
from tkinter import *
root = Tk()
root.title("Root widget")
root.mainloop()
window = Toplevel()
window.title("Window widget")
window.mainloop()
On running above code "Root" widget is getting created. On closing "Root", two widgets are created with one titled "Window widget" and other being unwanted. On closing unwanted widget, "Window widget" is also getting destroyed.
What is actually happening here and how to overcome?
Another sample:
class ldo(Frame):
def __init__(self, master = None):
Frame.__init__(self,master)
self.grid()
self.appOutline()
def appOutline(self):
top = self.winfo_toplevel()
self.menuBar = Menu(top)
top["menu"] = self.menuBar
self.subMenu1 = Menu(self.menuBar)
self.menuBar.add_cascade(label = "File", menu = self.subMenu1)
app = ldo()
app.master.title("Sample UI")
app.mainloop()
On the other hand, this code is using winfo_toplevel() where the widget looks perfectly fine. Here, my assumption is, Frame plays a role of creating widget and winfo_toplevel() is an enhancing tool to other tkinter items. But would like to know what it does actually.
However, below snippet is not working:
winf = winfo_Toplevel()
winf.title("Winfo Widget")
winf.mainloop()
And returning such error:
winf = winfo_Toplevel()
NameError: name 'winfo_Toplevel' is not defined
What is the exact difference between Tk(), Toplevel() and winfo_Toplevel(). What and when should one be used effectively. Looking for really a better understanding.
On running above code "Root" widget is getting created. On closing "Root", two widgets are created with one titled "Window widget" and
other being unwanted. On closing unwanted widget, "Window widget" is
also getting destroyed. What is actually happening here and how to
overcome?
When you create any widget with the absence of an actual Tk() instance, a Tk() instance automatically gets created, thus resulting in an unwanted Toplevel-like widget when the second part of the first code snippet runs. Additionally, when a widget gets created with the absence of master option, it is assumed that the instance is a child of one of the Tk instances, in above case, there's only one, and that's the one that got automatically created. When a parent gets destroyed all widgets that are under it also gets destroyed, so when you close the unwanted widget that is an instance of Tk, the Toplevel instance also gets destroyed as its parent is destroyed.
On the second part, winfo_toplevel refers to the automatically created Tk instance again and creates other children with that automatically created Tk as the parent, which should be technically fine but would be harder to maintain as a code, than the standard ways of creating the same GUI I'd presume.
winf = winfo_Toplevel()
winf.title("Winfo Widget")
winf.mainloop()
In the code piece above, unless imported or otherwise defined winfo_Toplevel has no meaning, first of all, it's not as same as winfo_toplevel as python is case sensitive. Secondly, even if python wasn't case sensitive, it would still throw an error as it is a method and it lacks the first positional argument, which is the object instance to the class of which the winfo_toplevel method is also defined for.
Essentially, you're trying to use a case-insensitive spelling of a method, as if it is a class name such as Toplevel or Tk, which winfo_toplevel has almost nothing to do with.
Examine the following code:
import tkinter as tk
root = tk.Tk()
root.title("This is the actual Tk instance, root")
toplevel = tk.Toplevel(root)
toplevel.title("This is a Toplevel, whose parent is root"),
r_lbl = tk.Label(text="""This label is a children to the default master,
as it lacks the first positional argument for an explicit parent
assignment.""")
r_lbl2 = tk.Label(r_lbl.winfo_toplevel(), text="""This label checks who the
toplevel parent for r_lbl is, and then selects that parent as a parent
to itself.""")
r_lbl3 = tk.Label(root, text="""This label will appear on root, as it's
explicitly passed as the first positional argument, which is the parent,
as root.""")
t_lbl = tk.Label(toplevel, text="""This label will appear on toplevel, as it's
explicitly passed as the first positional argument, which is the parent,
as toplevel.""")
t_lbl2 = tk.Label(t_lbl.winfo_toplevel(), text="""This label checks who the
toplevel parent for t_lbl is, and then selects that parent as a parent
to itself.""")
r_lbl.pack()
r_lbl2.pack()
r_lbl3.pack()
t_lbl.pack()
t_lbl2.pack()
root.mainloop()
In conclusion, Tk, while being a Toplevel widget, is also the tcl interpreter for the entire GUI that runs in a thread. There can be more than one present, which is discouraged as usually multiple instances of it is unjustified, but there also has to be at least one instance present in order to have a GUI.
Toplevel can be considered to be the only visual part of a Tk instance, and it can be used when there is a need for multiple window-like widgets.
Finally, winfo_toplevel is merely a method returns the reference for the Toplevel-like parent that a widget is in, be the parent an instance of a Toplevel or a Tk.

tkinter clicks on child do not propagate to parent

Why doesn't clicking on a child element propagate to the parent?
from tkinter import *
root = Tk()
def handler(event):
print('clicked at', event.x, event.y)
frame = Frame(root, width=100, height=100)
label = Label(frame, text="Label")
frame.bind('<Button-1>', handler)
frame.pack()
label.pack(side=TOP)
root.mainloop()
When I run that, clicking on the label doesn't fire the handler. I've understood that events propagate to parents by default and if you didn't want that, you'd have to return "break"
You are incorrect in your original understanding that events propagate to their parent. They do not.
Admittedly, there's an edge case for widgets which are a direct descendant of a toplevel or root window. Even there, it's not that they are propagating to their parent, but rather they are being handled by other bindings as defined by the bind tags, and by default every widget has it's toplevel window as one of it's bind tags.
If you want to set a binding to work everywhere you can use the bind_all method, since each widget has an "all" bindtag by default. Another option is to give several widgets the same bindtag (using the bindtags method), then bind to that bindtag with bind_class. Which choice you make depends on what you are trying to accomplish.
bindtags are extremely powerful -- arguably more powerful than any binding mechanisms from any other toolkit. For example, if you need to have events propagate you can do that by adjusting the bindtags of every widget to include all of its ancestors. In my experience, however, such shenanigans is rarely ever needed.
You're mistaken. "break" causes that event to not propagate to other handlers for the widget that was clicked on.
In other words, if you bound your action to label and then you bound another action to the first button onto label, both callbacks will be called (unless you return "break" from the first one to be called.)
I'm not sure of a workaround though ... (We might need to wait for BryanOakley to show up ;)

How to capture events on tkinter child widgets?

In the following block, clicking on a_frame triggers the event handler on_frame_click, but clicking on a_label which is a child of a_frame does not. Is there a way to force a_frame to trap and handle events which originated on it's children (preferably with out having to add handlers to the children directly)? I am using Python 3.2.3.
import tkinter
def on_frame_click(e):
print("frame clicked")
tk = tkinter.Tk()
a_frame = tkinter.Frame(tk, bg="red", padx=20, pady=20)
a_label = tkinter.Label(a_frame, text="A Label")
a_frame.pack()
a_label.pack()
tk.protocol("WM_DELETE_WINDOW", tk.destroy)
a_frame.bind("<Button>", on_frame_click)
tk.mainloop()
Yes, you can do what you want, but it requires a bit of work. It's not that it's not supported, it's just that it's actually quite rare to need something like this so it's not the default behavior.
TL;DR - research "tkinter bind tags"
The Tkinter event model includes the notion of "bind tags". This is a list of tags associated with each widget. When an event is received on a widget, each bind tag is checked to see if it has a binding for the event. If so, the handler is called. If not, it continues on. If a handler returns "break", the chain is broken and no more tags are considered.
By default, the bind tags for a widget are the widget itself, the widget class, the tag for the toplevel window the widget is in, and finally the special tag "all". However, you can put any tags you want in there, and you can change the order.
The practical upshot of all this? You can add your own unique tag to every widget, then add a single binding to that tag that will be processed by all widgets. Here's an example, using your code as a starting point (I added a button widget, to show this isn't something special just for frames and labels):
import Tkinter as tkinter
def on_frame_click(e):
print("frame clicked")
def retag(tag, *args):
'''Add the given tag as the first bindtag for every widget passed in'''
for widget in args:
widget.bindtags((tag,) + widget.bindtags())
tk = tkinter.Tk()
a_frame = tkinter.Frame(tk, bg="red", padx=20, pady=20)
a_label = tkinter.Label(a_frame, text="A Label")
a_button = tkinter.Button(a_frame, text="click me!")
a_frame.pack()
a_label.pack()
a_button.pack()
tk.protocol("WM_DELETE_WINDOW", tk.destroy)
retag("special", a_frame, a_label, a_button)
tk.bind_class("special", "<Button>", on_frame_click)
tk.mainloop()
For more on bindtags, you might be interested in my answer to the question How to bind self events in Tkinter Text widget after it will binded by Text widget?. The answer addresses a different question than the one here, but it shows another example of using bind tags to solve real world problems.
I can't seem to find a direct method of automatically binding to child widgets (though there are methods of binding to an entire class of widgets and to all widgets in an application), but something like this would be easy enough.
def bind_tree(widget, event, callback, add=''):
"Binds an event to a widget and all its descendants."
widget.bind(event, callback, add)
for child in widget.children.values():
bind_tree(child, event, callback, replace_callback)
Just thought of this, but you could also put a transparent widget the size of a_frame on top of everything as a child of a_frame and bind the <Button> event to that, and then you could refer to a_frame as e.widget.master in the callback in order to make it reusable if necessary. That'd likely do what you want.
Based on what it says in the Levels of Binding section of this online Tkinter reference, it sounds like it's possible because you can bind a handler to three different levels.
To summarize:
Instance Level: Bind an event to a specific widget.
Class Level: Bind an event to all widgets of a specific class.
Application Level: Widget independent -- certain events always invoke a specific handler.
For the details please refer to the first link.
Hope this helps.
Depending on what you're trying to do, you could bind everything
print(a_label.bindtags()) # ('.!frame.!label', 'Label', '.', 'all')
tk.bind_class('.', "<Button>", on_frame_click)

Categories