c is the canvas, and I want to bind certain key( (ex)d ) to another function using c.bind_all()
IDK the event name that I should use..
c.bind_all('<KeyPress-D>', func)
dosen't work..
According to the canonical tcl/tk documentation there are three ways to bind to a normal character (other than "<" or space):
c.bind_all("d", func)
c.bind_all("<d>", func)
c.bind_all("<KeyPress-d>", func)
Related
Is it possible to have arguments and an event listener on a function? I have two entries that I want to clear on <FocusIn>. I thought it would be simple and bind delete like self.minutes.bind("<FocusIn>", self.minutes.delete(0, "end)), but no that would be too easy. So I created a function to wipe any entry I want whenever focused. My function is simply:
def entry_clear(entry, e):
entry.delete(0, "end")
This results in entry_clear() missing 1 required positional argument: 'e' but if I use it with self it works fine like:
def entry_clear(self, e):
self.minutes.delete(0, "end")
But of course now I have to specify the exact entry I want in the function, rather than being used for any entry. Thanks for any help.
You do not really need to pass the widget itself because tkinter passes an Event object implicitly with bind. This Event object has an attribute called widget which will be the widget that originally triggered the event. So you can just delete the items of that widget directly:
def entry_clear(self, e): # `e` is `Event` object
e.widget.delete(0, "end")
Now to answer your original question:
Is it possible to have arguments and an event listener on a function?
Yes it is possible, but it depends on how you use bind, a fairly common way is like:
ent.bind('<1>', lambda event: callback(event, ent))
Instead of this, I would always use the first method.
I was wondering if there is any way to get around using global variables in the callback functions that are used in bind in tkinter.
What I refer to is:
canvas = Canvas(root, width=500, height=500)
canvas.bind('<B1-Motion>', func)
where func is now some function that is triggered when the mouse is dragged. What I want is something like:
canvas.bind('<B1-Motion>', func(arg))
In combination with:
def func(event, arg):
commands
I can see from https://docs.python.org/3/library/tkinter.html that one argument, which is the event itself, is given to the callback function, but it seems like waste of potential to not give this method any way to modify its callback in a different way.
Maybe I am mistaken and there is some technical reason why that is impossible in general or maybe there is an alternative to bind.
I was basically expecting something like:
buttoname = Button(...,...,..., command = Lambda: func(arg))
If anyone has any pointers, it would be much appreciated.
regards
Use a lambda that receives the event parameter and passes it along.
canvas.bind('<B1-Motion>', lambda e: func(e, arg))
Info
python 3
tkinter
Goal
I have a piece of code, that generates a custom menu for me. Now I want to change the color of the single buttons within the menu when the mouse hovers over them.
Code
for i in cmd:
button = Button([..set some stuff here..])
button.bind("<Enter>", lambda name: button.config(bg=hovercolor))
button.bind("<Leave>", lambda name: button.config(bg=color))
button.pack()
My idea was to bind ENTER and LEAVE events to every button. To get the lambda function to work it needs a name(in this case name, but I don't care about the name).
Problem
If the mouse hovers over any button in my generated menu the color of the last button is changed.
I think that is caused by the name of the lambda function. every button generates a new lambda function with the same name, overwriting the previous lambda function in the python environment.
I'm searching for:
a way to generate the names for the lambda functions
or
another way to change the color if the mouse hovers over the button.
Solved
for i in cmd:
button = Button([..set some stuff here..])
button.bind("<Enter>", lambda name, button=button: button.config(bg=hovercolor))
button.bind("<Leave>", lambda name, button=button: button.config(bg=color))
button.pack()
The issue is the binding of button. Your lambda expression doesn't define it, so it needs a closure to be found in the outer scope (the loop). Since the value changes each time through the loop, by the time the lambda functions are run the button variable points at the last button you created, not the one it pointed to when the lambda function was defined.
You can work around this by saving each button object as a default argument:
button.bind("<Enter>", lambda name, button=button: button.config(bg=hovercolor))
The button=button argument makes button a local variable within the lambda, so the changing outer definition doesn't matter. No closures necessary!
The reason it's happening is as you said, the name gets overwritten and the lambdas all run on the same variables.
What you could do is create a factory of callbacks, so you'd pass the names to the function, and the function would return the callback that'd get called.
def create_callback(button, bg_color):
lambda x: button.config(bg=bg_color)
button.bind("<Enter>", create_callback(button, hovercolor))
I was surprised when a function that I wrote in python/tkinter and binded to Ctrl-b behaved strangely (specific: it was losing the value of the selected text, so that text.index(SEL_FIRST) was undefined).
I was surprised when, after changing the more improbable things I binded it instead to something else - and it worked!
I searched but didnt find anything: is Control-b binded to something default in tkinter???
alessandro
If you are talking about the text widget, from the official tk text widget documentation:
"The Left and Right keys move the
insertion cursor one character to the
left or right; they also clear any
selection in the text...Control-b and
Control-f behave the same as Left and
Right, respectively."
Thank you Bryan, here's the link - obviously on effbot http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm:
(...)
You could use the bind_class method to
modify the bindings on the class
level, but that would change the
behavior of all text widgets in the
application. An easier solution is to
prevent Tkinter from propagating the
event to other handlers; just return
the string “break” from your event
handler:
def ignore(event):
return "break"
text.bind("<Return>", ignore)
or
text.bind("<Return>", lambda e: "break")
By the way, if you really want to
change the behavior of all text
widgets in your application, here’s
how to use the bind_class method:
top.bind_class("Text", "<Return>", lambda e: None)
But there are a lot of reasons why you
shouldn’t do this. For example, it
messes things up completely the day
you wish to extend your application
with some cool little UI component you
downloaded from the net. Better use
your own Text widget specialization,
and keep Tkinter’s default bindings
intact:
class MyText(Text):
def __init__(self, master, **kw):
apply(Text.__init__, (self, master), kw)
self.bind("<Return>", lambda e: "break")
Consider the following code:
text = Entry(); text.pack()
def show(e):
print text.get()
text.bind('<Key>', show)
Let's say I put the letters ABC in the Entry, one by one. The output would be:
>>>
>>> A
>>> AB
Note that when pressing A, it prints an empty string. When I press B, it prints A, not AB. If i don't press anything after C, it will never be shown. It seems that the Entry content is only updated after the binded command has returned, so I can't use the actual Entry value in that function.
Is there any way to get an updated Entry value to use inside a binded command?
You could replace the <Key> event with the <KeyRelease> event. That should work.
Here is a list of events: http://infohost.nmt.edu/tcc/help/pubs/tkinter/events.html#event-types
The reason for this has to do with Tk "bindtags". Bindings are associated with tags, and the bindings are processed in tag order. Both widget names and widget classes are tags, and they are processed in that order (widget-specific bindings first, class bindings second).
For that reason, any time you press a key your widget specific binding will fire before the class binding has a chance to modify the widget.
There are many workarounds. The simplest is to bind to <KeyRelease> since the class bindings happen on the key press. There are other solutions that involve either adding or rearranging bind tags, or using the built-in data validation features of the entry widget. Which is best depends on what you're really trying to accomplish.
For more information on the data validation functions, see this question: Interactively validating Entry widget content in tkinter
For a more comprehensive answer, see Tkinter: set StringVar after <Key> event, including the key pressed