In Tkinter how do i remove focus from a widget? - python

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='')

Related

Add ScrollBar to a window of labels [duplicate]

This question already has answers here:
Adding a scrollbar to a group of widgets in Tkinter
(3 answers)
Closed 4 years ago.
My code is working well in itself, but doesn't scroll through the Labels (which is what i'm trying to achieve).
I don't want to use canvas or listbox or anything.
import tkinter as tk
master = tk.Tk()
scrollbar = tk.Scrollbar(master).pack(side=tk.RIGHT, fill=tk.Y,command=tk.yview)
label = tk.Label(text="llklkl")
label.place(x=100,y=500)
label2 = tk.Label(text="llklkl")
label2.place(x=1000,y=5000)
tk.mainloop()
Hello and welcome to SO.
The tkinter Scrollbar widget sadly can not be used on a _tkinter.tkapp object, i.e. your main window called master. From effbot.org:
This widget is used to implement scrolled listboxes, canvases, and text fields.
and
The Scrollbar widget is almost always used in conjunction with a Listbox, Canvas, or Text widget. Horizontal scrollbars can also be used with the Entry widget.
That means that you absolutely HAVE to create some widget inside your main window in order to be able to scroll anything, you can`t just scroll the window itself.
That being said, if you wanted to add a Scrollbar to, let's say, a Listbox, that's how you would do it (also taken from the above mentioned website, you should really check it out):
First of all, you have to set the widget’s yscrollcommand callbacks to the set method of the scrollbar.
Secondly, you have to set the scrollbar’s command to the yview method of the widget, much like you did already, but like name_of_object.yview, not tk.yview.
import tkinter as tk
master = tk.Tk()
scrollbar = tk.Scrollbar(master)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
listbox = tk.Listbox(master, yscrollcommand=scrollbar.set)
for i in range(20):
listbox.insert(tk.END, str(i))
listbox.pack(side=tk.LEFT, fill=tk.BOTH)
scrollbar.config(command=listbox.yview)
master.mainloop()
Also, pack the scrollbar in a seperate line. This will produce a window with numbers from 1 to 50 in a scrollable Listbox widget. If i get you right, you want to be able to scroll between your labels? well, i guess you'll have to use some kind of wrapping widget for that, i would recommend a Canvas. But that's really up to you and i'm sure you'll figure it out yourself. If you need any more help, let me know - but please read the docs before asking ;-)

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)

Simple bind function doesn't work in 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.

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)

Python Tkinter Tix: How to use ScrolledWindow with grid in Tix NoteBook

I'm adding several widgets to a Frame which is located in a tix.NoteBook. When there are too much widgets to fit in the window, I want to use a scrollbar, so I put tix.ScrolledWindow inside that Frame and add my widgets to this ScrolledWindow instead.
The problem is that when using the grid() geometry manager, the scrollbar appears, but it is not working (The drag bar occupies the whole scroll bar).
from Tkinter import *
import Tix
class Window:
def __init__(self, root):
self.labelList = []
self.notebook = Tix.NoteBook(root, ipadx=3, ipady=3)
self.notebook.add('sheet_1', label="Sheet 1", underline=0)
self.notebook.add('sheet_2', label="Sheet 2", underline=0)
self.notebook.add('sheet_3', label="Sheet 3", underline=0)
self.notebook.pack()
#self.notebook.grid(row=0, column=0)
tab1=self.notebook.sheet_1
tab2=self.notebook.sheet_2
tab3=self.notebook.sheet_3
self.myMainContainer = Frame(tab1)
self.myMainContainer.pack()
#self.myMainContainer.grid(row=0, column=0)
scrwin = Tix.ScrolledWindow(self.myMainContainer, scrollbar='y')
scrwin.pack()
#scrwin.grid(row=0, column=0)
self.win = scrwin.window
for i in range (100):
self.labelList.append((Label(self.win)))
self.labelList[-1].config(text= "Bla", relief = SUNKEN)
self.labelList[-1].grid(row=i, column=0, sticky=W+E)
root = Tix.Tk()
myWindow = Window(root)
root.mainloop()
Whenever I change at least one of the geometry managers from pack() to grid(), the problem occurs. (Actually, I'd prefer using grid() for all containers.)
When I don't use the NoteBook widget, the problem does not occur either. The other examples here all seem to rely on pack().
Any ideas?
Many thanks,
Sano
I solved it without using ´tix.scrolledWindow´. Instead, I went for the autoscrollbar suggested by Fred Lundh here.
The main problem was the adaption to the NoteBook widget. First, I tried to put the scrollbar to the root, so that they would surround the whole window. Now, I wanted to change the hook for the scrollbar whenever I changed a tab, but the ´raisecmd´ of the Notebook did not work. Next, I thought of using the configure event on each tab - whenever a new tab is raised, its size changes and configure is called.
Well, after much trying without ever being satisfied I changed my approach and put the scrollbars inside of the tabs. The tabs and all subcontainers must get the ´grid_columnconfigure(0, weight=1)´ and ´grid_rowconfigure(0, weight=1)´ settings, or else they will not grow with the tabs.

Categories