You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
tkinter focus_get() with non-tkinter Tk widget #88758
The purpose of focus_get() is to return the widget that currently has the focus. It tries to convert its result to a tkinter widget, which can fail, because not all Tk widgets are known to tkinter. Consider this, for example:
Output, with menu clicked after a couple seconds (on Linux):
None<tkinter.Tk object .><tkinter.Tk object .>Exceptionin Tkinter callback
Traceback (most recent call last):
File "/home/akuli/.local/lib/python3.10/tkinter/__init__.py", line 1916, in__call__returnself.func(*args)
File "/home/akuli/.local/lib/python3.10/tkinter/__init__.py", line 838, in callit
func(*args)
File "/home/akuli/porcu/foo.py", line 4, in print_focused_widget
print(repr(root.focus_get()))
File "/home/akuli/.local/lib/python3.10/tkinter/__init__.py", line 782, in focus_get
returnself._nametowidget(name)
File "/home/akuli/.local/lib/python3.10/tkinter/__init__.py", line 1531, in nametowidget
w = w.children[n]
KeyError: '#!menu'
Some nametowidget() calls in tkinter/init.py already handle this correctly. Consider winfo_children(), for example:
try:
# Tcl sometimes returns extra windows, e.g. for
# menus; those need to be skipped
result.append(self._nametowidget(child))
except KeyError:
pass
Forgot to mention: The correct fix IMO would be to return None when a KeyError occurs. This way code like focus_get() == some_tkinter_widget would always do the right thing, for example.
E-Paine commented on Jul 9, 2021
E-Painemannequin
Mannequin
More actions
I agree with Akuli that raising a KeyError is not expected behaviour (combined with the fact this is caught elsewhere), and therefore is probably a regression.
While we could use winfo class to determine the type of Tk widget, this would probably require a reasonably sized refactor of tkinter (and we would still need to support cases when it's a type we don't know). Therefore, I think returning None is the best solution.
Akuli, would you like to create a pull request for this?
Sorry, I should specify that we would use winfo class in order to then return a new tkinter object for the existing Tk widget (which currently cannot be done)
I found bpo-734176 before I created this. It is NOT a duplicate. While bpo-734176 is about menus, this one is about focus_get(), and not necessarily related to menus. In fact, I initially noticed this with an "open file" dialog, not with a menu.
I'm not putting my address into your CLA, thank you very much.
Akuli, what tk widgets do you think are not known to tkinter? In any case, tk menu is known to tkinter.
I cannot reproduce when running on Windows with 3.10.0b3: Add "print(root.children)" (after add_cascade) results in {'!menu': <tkinter.Menu object .!menu>, '!menu2': <tkinter.Menu object .!menu2>}. The names are created in tkinter.py lines 2564-2573.
I then see 'None' once and then '<tkinter.Tk object .>' indefinitely even while hovering over and clicking 'click me' and the dropdown. If I click outside the tk box, the print returns to 'None'.
Maybe there is an OS difference in what is considered to have 'focus'.
Key '#!menu' looks like '!menu' with '#' prepended. Someone could try changing the tkinter code referenced above and see if the change appears in the bad key. Also check the contents of root.children.
I am not quite convinced that this is a duplicate of bpo-734176. The latter is about tearoff clones and nothing is cloned here. But I do notice that number 'names were also prefixed with '#'. What happens if 'tearoff=0' is added to the cascade so that it is not even clonable. The tkinter naming of instances after the class was added less than 10 years ago.
Unfortunately I don't know any real-world examples of this on Windows. The open file dialog works very differently on Windows: it uses the native Windows dialog, whereas on Linux, it's implemented in Tcl.
Meanwhile, here's a platform-independent toy example:
Traceback (most recent call last):
File "C:\Programs\Python310\lib\tkinter\__init__.py", line 1921, in __call__returnself.func(*args)
File "C:\Programs\Python310\lib\tkinter\__init__.py", line 839, in callit
func(*args)
File "C:\Programs\Python310\lib\tkinter\__init__.py", line 783, in focus_getreturnself._nametowidget(name)
File "C:\Programs\Python310\lib\tkinter\__init__.py", line 1536, in nametowidget
w = w.children[n]
KeyError: 'e'
Is catching KeyError in the following
try:
# Tcl sometimes returns extra windows, e.g. for
# menus; those need to be skipped
result.append(self._nametowidget(child))
except KeyError:
pass
really correct? It appears to skip things that *can* get focus by key or mouse action. But what choice is there?
Silently failing when asked to focus on something is even less obviously correct. For 'widget = root.focus_get' to assign None to widget is not obviously useful as it likely just delays the error.
Akuli commented on Jul 17, 2021
Akulimannequin
MannequinAuthor
More actions
Here are the options:
Do nothing. My program will error in some corner cases.
Change it to return None, so widget.focus_get() is not None no longer means "this application doesn't have focus", but rather "this application doesn't have focus or the focused widget was not created in tkinter". Note that focus_get() can already return None, and we would just add one more situation where it does so.
Change tkinter so that it doesn't matter whether a widget was created in tkinter or not. This doesn't seem to be easy.
Akuli commented on Jul 17, 2021
Akulimannequin
MannequinAuthor
More actions
Typo in previous message: I meant widget.focus_get() is None. It currently means "this application doesn't have focus", while is not None currently means "this application has focus".
Fixed by GH-152337. focus_get(), focus_displayof(), focus_lastfor() and winfo_containing() now return None instead of raising KeyError when the focused widget was not created by tkinter (for example a torn-off menu).
Returning None is the pragmatic fix. The proper long-term solution is to have nametowidget() adopt any existing Tcl widget by its pathname and return a wrapper; these methods could then return that wrapper instead of None. That is left as future work.
Activity
Akuli commentedon Jul 9, 2021
The purpose of focus_get() is to return the widget that currently has the focus. It tries to convert its result to a tkinter widget, which can fail, because not all Tk widgets are known to tkinter. Consider this, for example:
Output, with menu clicked after a couple seconds (on Linux):
Some nametowidget() calls in tkinter/init.py already handle this correctly. Consider winfo_children(), for example:
Akuli commentedon Jul 9, 2021
Forgot to mention: The correct fix IMO would be to return None when a KeyError occurs. This way code like
focus_get() == some_tkinter_widgetwould always do the right thing, for example.E-Paine commentedon Jul 9, 2021
I agree with Akuli that raising a KeyError is not expected behaviour (combined with the fact this is caught elsewhere), and therefore is probably a regression.
While we could use
winfo classto determine the type of Tk widget, this would probably require a reasonably sized refactor of tkinter (and we would still need to support cases when it's a type we don't know). Therefore, I think returningNoneis the best solution.Akuli, would you like to create a pull request for this?
E-Paine commentedon Jul 9, 2021
Sorry, I should specify that we would use
winfo classin order to then return a new tkinter object for the existing Tk widget (which currently cannot be done)serhiy-storchaka commentedon Jul 9, 2021
It is a duplicate of bpo-734176.
Akuli commentedon Jul 9, 2021
I found bpo-734176 before I created this. It is NOT a duplicate. While bpo-734176 is about menus, this one is about focus_get(), and not necessarily related to menus. In fact, I initially noticed this with an "open file" dialog, not with a menu.
I'm not putting my address into your CLA, thank you very much.
terryjreedy commentedon Jul 9, 2021
Akuli, what tk widgets do you think are not known to tkinter? In any case, tk menu is known to tkinter.
I cannot reproduce when running on Windows with 3.10.0b3: Add "print(root.children)" (after add_cascade) results in {'!menu': <tkinter.Menu object .!menu>, '!menu2': <tkinter.Menu object .!menu2>}. The names are created in tkinter.py lines 2564-2573.
I then see 'None' once and then '<tkinter.Tk object .>' indefinitely even while hovering over and clicking 'click me' and the dropdown. If I click outside the tk box, the print returns to 'None'.
Maybe there is an OS difference in what is considered to have 'focus'.
Key '#!menu' looks like '!menu' with '#' prepended. Someone could try changing the tkinter code referenced above and see if the change appears in the bad key. Also check the contents of root.children.
terryjreedy commentedon Jul 9, 2021
I am not quite convinced that this is a duplicate of bpo-734176. The latter is about tearoff clones and nothing is cloned here. But I do notice that number 'names were also prefixed with '#'. What happens if 'tearoff=0' is added to the cascade so that it is not even clonable. The tkinter naming of instances after the class was added less than 10 years ago.
Akuli commentedon Jul 9, 2021
Unfortunately I don't know any real-world examples of this on Windows. The open file dialog works very differently on Windows: it uses the native Windows dialog, whereas on Linux, it's implemented in Tcl.
Meanwhile, here's a platform-independent toy example:
Also, thanks for reopening!
terryjreedy commentedon Jul 17, 2021
Is catching KeyError in the following
try:
# Tcl sometimes returns extra windows, e.g. for
# menus; those need to be skipped
result.append(self._nametowidget(child))
except KeyError:
pass
really correct? It appears to skip things that *can* get focus by key or mouse action. But what choice is there?
Silently failing when asked to focus on something is even less obviously correct. For 'widget = root.focus_get' to assign None to widget is not obviously useful as it likely just delays the error.
Akuli commentedon Jul 17, 2021
Here are the options:
Do nothing. My program will error in some corner cases.
Change it to return
None, sowidget.focus_get() is not Noneno longer means "this application doesn't have focus", but rather "this application doesn't have focus or the focused widget was not created in tkinter". Note thatfocus_get()can already return None, and we would just add one more situation where it does so.Change tkinter so that it doesn't matter whether a widget was created in tkinter or not. This doesn't seem to be easy.
Akuli commentedon Jul 17, 2021
Typo in previous message: I meant
widget.focus_get() is None. It currently means "this application doesn't have focus", whileis not Nonecurrently means "this application has focus".TheLizzard commentedon Sep 19, 2023
I ran into this problem with this code:
The code raises a
KeyErroron Ubuntu 22.04 but not on Windows 11.Using this to investigate:
I get that on Windows,
tcldoesn't even try to name the widgets inside the dialogbox but on Ubuntu:Version 7.6
gh-88758: Handle non-tkinter widgets in tkinter focus methods (GH-152337
serhiy-storchaka commentedon Jun 26, 2026
Fixed by GH-152337.
focus_get(),focus_displayof(),focus_lastfor()andwinfo_containing()now returnNoneinstead of raisingKeyErrorwhen the focused widget was not created by tkinter (for example a torn-off menu).Returning
Noneis the pragmatic fix. The proper long-term solution is to havenametowidget()adopt any existing Tcl widget by its pathname and return a wrapper; these methods could then return that wrapper instead ofNone. That is left as future work.[3.14] gh-88758: Handle non-tkinter widgets in tkinter focus methods (G…
[3.15] gh-88758: Handle non-tkinter widgets in tkinter focus methods (G…
[3.13] gh-88758: Handle non-tkinter widgets in tkinter focus methods (G…