Simple bind function doesn't work in python - python

I'm currently creating an adventure game and I want to bind alt+a to my callback. It doesn't do what I want, so I have two questions:
Is it possible to bind a function to a Label, too?
Why does this (simplyfied) code doesn't work?
Here is the code:
import tkinter as tk
dw = tk.Tk()
dw.title('Hearts')
def play(event):
print('This is the test.')
areal = tk.Frame(master=dw, width=1200, height=600, bg='blue')
areal.pack_propagate(0)
areal.pack(fill=tk.BOTH, expand=bool(dw))
areal.bind("<Alt-A>", play)
dw.mainloop()
It doesn't give me an error, but it doesn't do anything when I click the Frame and afterwards press alt+a. What is wrong here?
EDIT:
import tkinter as tk
def go_fwd(event):
areal.focus_set()
print(event.x, event.y)
dw = tk.Tk()
dw.title('Adventure')
areal = tk.Frame(master=dw, width=20000, height=600, bg='blue')
areal.pack_propagate(0)
areal.pack(fill=tk.BOTH, expand=bool(dw)-100)
areal.focus_set()
dw.bind("<Alt-A>", go_fwd)
enter = tk.Frame(master=dw, width=20000, height=100, bg='cyan')
enter.pack(fill=tk.X)
enterentry = tk.Text(master=enter, width=100, height=4, bg='white')
enterentry.pack()
enterbutton = tk.Button(master=enter, text='Senden', bg='red')
enterbutton.pack()
dw.mainloop()
Here is the complete code.

Is it possible to bind a function to a Label, too?
You can bind to any widget you want. However, if you bind key events, the bindings will only work if the widget has focus. By default, most widgets other than Entry and Text don't get focus unless you explicitly set the focus to them.
Note: only one widget can have keyboard focus at a time.
You can also set a binding to the root window, which will cause it to fire no matter what widget has focus.
For a more thorough explanation of how key bindings are processed, see this answer: https://stackoverflow.com/a/11542200/7432
Why does this (simplyfied) code doesn't work?
It doesn't work the way you expect because the binding is on a Frame widget, but that widget doesn't have the keyboard focus. You could give it focus with something like this:
areal.focus_set()
Or, you could only give it focus after you click on the frame, by creating a binding on a mouse click:
areal.bind("<1>", lambda event: areal.focus_set())
Note: you are binding to a capital "A", so make sure when you test that you're pressing control-alt-a

You need to bind to dw instead of your frame.
So, you can do dw.bind("<Alt-A>", play).
A minor note, Alt-A will bind to the uppercase A as expected, so you'd have to click Alt+Shift+A on your keyboard. Doing Alt+A on your keyboard won't work, you'd have to bind to Alt-a for this to work.
The main window has keyboard focus. Or, alternatively you can leave the bind on the frame and just do areal.focus_set() to set the focus to the frame.

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"

Tkinter '<Configure>' event prevents widgets from loading properly

I am currently coding a small app in Tkinter and need to resize a Frame any time the window is resized to keep the aspect ratio of the frame.
I have used the Configure event for that. However, when using this event, the widgets of the window do not show up until I hover my mouse above them.
I, of course, don't want this behaviour to happen.
I have tried binding the configure event to the root window before and after the creation of all the widgets. Without success.
Furthermore, I have tried waiting before I call the configure event with the sleep method.
I have also tried adding a thread which executes binds the event a few seconds later. It does work but when I create new widgets, those new widgets are not properly loaded. I was wondering I there was a way around this.
The bind is done in the init function of my app class
self.root = tk.Tk()
self.root.bind("<Configure>", self.changeWindowSize)
The call back is another class function
def changeWindowSize(self, *args):
self.root.update()
# SOME CALCULATIONS
# Replace the UI
self.ui.place_forget()
self.ui.place(relx=0.25+(0.75 - ui_width)/2, rely=0.05+(0.75 - ui_height)/2, relwidth=ui_width, relheight=ui_height)
Edit:
Here is how to reproduce it:
import tkinter as tk
root = tk.Tk()
root.geometry("1100x600")
def callback(event):
root.update()
root.bind("<Configure>", callback)
for i in range(10):
btn = tk.Button(root, text="Button " + str(i) )
btn.place(relx=0.1*i, rely=0, relwidth=0.1, relheight=1)
root.mainloop()
This issue is now obvious. The root.update causes this behaviour.

How can I set the focus to a widget (that isn't an entry) by clicking on it?

The reason I need to do this is because I need to change the text of a label without having to use entries; I want to use events instead.
I have tried this:
import tkinter as tk
root = tk.Tk()
root.bind("<Button-1>", lambda _: root.focus())
l = tk.Label(root, width=50, height=50, bg="white")
l.bind("<Button-1>", lambda _: l.focus())
l.bind("1", lambda _: l.config(bg="yellow"))
l.bind("2", lambda _: l.config(bg="white"))
l.pack()
root.mainloop()
When I ran the program, I expected to be able to change the colour of the label l to yellow by clicking on it (which I thought would set the focus to it) then pressing 1, and changing it back to white by pressing 2; provided that I didn't click outside of the label and set the focus to the root widget (where the keys 1 and 2 weren't bound to any callback).
I know that you can bind keys to callbacks (tested it), and I also know that it is possible to set the focus to widgets which aren't entries (tested that too), yet this doesn't seem to work.
Can anybody help me?
The problem is that you have two bindings for a button click: one on the label widget itself and one on the root window. Because of the way that events are processed, the binding on the root window will fire after the event on the label. That means that whatever focus you set on the label binding will get undone with the binding on the root window.
One solution is to change your binding on the click to set the focus to whatever was clicked on. With that, you don't need to set a binding on the label widget for a click.
root.bind("<Button-1>", lambda event: event.widget.focus_set())
Another solution would be to modify your binding on the label to prevent the binding on the root window from firing. You can do that by returning the string "break" from the function that is called.
def callback(event):
l.focus()
return "break"
l.bind("<Button-1>", callback)

Python tkinter scrollbar active state

Not sure what else to call 'active'. Is it possible to have the scrollbar, once clicked on to remain 'active'? Another words once I click on the scrollbar I would like to be able to move the scrollbar with the keyboard(left/right arrow keys) or the mouse. Is this possible? If so what do I have to do to accomplish it?
Technically it should be possible, by giving the scrollbar the keyboard focus and then adding some custom bindings. That's a fairly unusual thing to do. Since the scrollbars are drawn with native widgets on Windows and the Mac, it might be impossible on those platforms.
What you probably want to do instead is set some bindings on the application as a whole, or on some sort of widget that typically gets focus such as a canvas or text widget. Your bindings can call the xview and yview commands and give it arguments to tell it how to scroll, which is exactly what the scrollbar does.
You can achieve this like this for example:
from Tkinter import *
root = Tk()
scrollbar = Scrollbar(root, takefocus = True)
scrollbar.pack( side = RIGHT, fill=Y )
mylist = Listbox(root, yscrollcommand = scrollbar.set )
scrollbar.bind("<Down>", lambda event: mylist.yview_scroll(1, "units"))
scrollbar.bind("<Up>", lambda event: mylist.yview_scroll(-1, "units"))
scrollbar.bind("<Button-1>", lambda event: scrollbar.focus_set())
for line in range(100):
mylist.insert(END, "This is line number " + str(line))
mylist.pack( side = LEFT, fill = BOTH )
scrollbar.config( command = mylist.yview )
mainloop()
The takefocus flag allows the scrollbar to maintain the focus. The focus is set with "<Button-1>" event. Key presses are also handled by the events and invoke the desired scroll behaviour on scrollable object.

In Tkinter how do i remove focus from a widget?

I'd like to remove focus from a widget manually.
You can focus to another dummy widget.
Edit
from Tkinter import *
def callback():
print master.focus()
master = Tk()
e = Entry(master)
e.pack()
e.focus()
b = Button(master, text="get", width=10, command=callback)
b.pack()
master.mainloop()
Focusing on a non-'focusable' widget will remove focus from another widget.
Set focus to another widget to remove focus from the target widget is a good idea. There are two methods for this: w.focus_set() and w.focus_force(). However, method w.focus_force() is impolite. It's better to wait for the window manager to give you the focus. Setting focus to parent widget or to the root window removes focus from the target widget.
Some widgets have takefocus option. Set takefocus to 0 to take your widget out of focus traversal (when user hits <Tab> key).
My solution is root.focus() it will remove widget focus.
If the dummy widget is Canvas then c.focus() will not work.
use c.focus_set() or c.tk.call('focus',c) to first focus on the canvas window itself.
That's because
c.focus()
... returns the id for the item that currently has the focus, or an empty string if no item has the focus. Reference
c.focus(id_) will focus on the item having id id_ within the canvas.
c.focus("") will remove the focus from any item in the canvas.
Hence (within some callback)
c.config(highlightthickness = 0) # to remove the highlight border on focus
c.foucs_set()
c.focus("") # just to be sure
The reason c.focus() functions differently is that within Tcl/Tk's Commands there's the "Primary" Command focus
as well as the Canvas-specific Widget Command focus
That's not an issue within the Tcl/Tk syntax but in the tkinter module c.focus() will call the underlying canvas-specific foucs.
From tkinter.py within the Canvas class Line 2549
def focus(self, *args):
"""Set focus to the first item specified in ARGS."""
return self.tk.call((self._w, 'focus') + args)
So the question may be a duplicate here, but the answer from #Bryan Oakley works perfectly for me in Python 3.8
root.focus_set()
Too easy...
If you use ttk widgets you can "remove" the focus ring by removing the color; for example on a button:
style = ttk.Style()
style.configure('TButton', focuscolor='')

Categories