Skip to content

Commit 9c95081

Browse files
gh-88758: Handle non-tkinter widgets in tkinter focus methods
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). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent bbf7786 commit 9c95081

3 files changed

Lines changed: 36 additions & 4 deletions

File tree

Lib/test/test_tkinter/test_misc.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,22 @@ def test_focus_methods(self):
407407
self.root.update()
408408
self.assertIs(self.root.focus_get(), b)
409409

410+
def test_focus_methods_unresolvable(self):
411+
# The focus may be on a widget that tkinter did not create and so
412+
# cannot map to an instance (e.g. a torn-off menu). The focus
413+
# methods return None instead of raising KeyError (gh-88758).
414+
menu = tkinter.Menu(self.root, tearoff=1)
415+
menu.add_command(label='Hello')
416+
tearoff = self.root.tk.call('tk::TearOffMenu', str(menu), 0, 0)
417+
self.addCleanup(self.root.tk.call, 'destroy', tearoff)
418+
self.root.update()
419+
self.assertRaises(KeyError, self.root.nametowidget, tearoff)
420+
421+
self.root.tk.call('focus', '-force', tearoff)
422+
self.root.update()
423+
self.assertIsNone(self.root.focus_get())
424+
self.assertIsNone(self.root.focus_displayof())
425+
410426
def test_grab(self):
411427
f = tkinter.Frame(self.root)
412428
f.pack()

Lib/tkinter/__init__.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -894,7 +894,10 @@ def focus_get(self):
894894
the focus."""
895895
name = self.tk.call('focus')
896896
if name == 'none' or not name: return None
897-
return self._nametowidget(name)
897+
try:
898+
return self._nametowidget(name)
899+
except KeyError:
900+
return None
898901

899902
def focus_displayof(self):
900903
"""Return the widget which has currently the focus on the
@@ -903,14 +906,20 @@ def focus_displayof(self):
903906
Return None if the application does not have the focus."""
904907
name = self.tk.call('focus', '-displayof', self._w)
905908
if name == 'none' or not name: return None
906-
return self._nametowidget(name)
909+
try:
910+
return self._nametowidget(name)
911+
except KeyError:
912+
return None
907913

908914
def focus_lastfor(self):
909915
"""Return the widget which would have the focus if top level
910916
for this widget gets the focus from the window manager."""
911917
name = self.tk.call('focus', '-lastfor', self._w)
912918
if name == 'none' or not name: return None
913-
return self._nametowidget(name)
919+
try:
920+
return self._nametowidget(name)
921+
except KeyError:
922+
return None
914923

915924
def tk_focusFollowsMouse(self):
916925
"""The widget under mouse will get automatically focus. Can not
@@ -1313,7 +1322,10 @@ def winfo_containing(self, rootX, rootY, displayof=0):
13131322
+ self._displayof(displayof) + (rootX, rootY)
13141323
name = self.tk.call(args)
13151324
if not name: return None
1316-
return self._nametowidget(name)
1325+
try:
1326+
return self._nametowidget(name)
1327+
except KeyError:
1328+
return None
13171329

13181330
def winfo_depth(self):
13191331
"""Return the number of bits per pixel."""
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:meth:`!tkinter.Misc.focus_get`, :meth:`!focus_displayof`,
2+
:meth:`!focus_lastfor` and :meth:`!winfo_containing` now return ``None``
3+
instead of raising :exc:`KeyError` when the widget was not created by
4+
:mod:`tkinter` (for example a torn-off menu).

0 commit comments

Comments
 (0)