Skip to content

Commit 90528ec

Browse files
miss-islingtonserhiy-storchakaclaude
authored
[3.15] gh-88758: Handle non-tkinter widgets in tkinter focus methods (GH-152337) (GH-152346)
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). (cherry picked from commit 5fed5ce) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ba4084f commit 90528ec

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
@@ -405,6 +405,22 @@ def test_focus_methods(self):
405405
self.root.update()
406406
self.assertIs(self.root.focus_get(), b)
407407

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

Lib/tkinter/__init__.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,10 @@ def focus_get(self):
827827
the focus."""
828828
name = self.tk.call('focus')
829829
if name == 'none' or not name: return None
830-
return self._nametowidget(name)
830+
try:
831+
return self._nametowidget(name)
832+
except KeyError:
833+
return None
831834

832835
def focus_displayof(self):
833836
"""Return the widget which has currently the focus on the
@@ -836,14 +839,20 @@ def focus_displayof(self):
836839
Return None if the application does not have the focus."""
837840
name = self.tk.call('focus', '-displayof', self._w)
838841
if name == 'none' or not name: return None
839-
return self._nametowidget(name)
842+
try:
843+
return self._nametowidget(name)
844+
except KeyError:
845+
return None
840846

841847
def focus_lastfor(self):
842848
"""Return the widget which would have the focus if top level
843849
for this widget gets the focus from the window manager."""
844850
name = self.tk.call('focus', '-lastfor', self._w)
845851
if name == 'none' or not name: return None
846-
return self._nametowidget(name)
852+
try:
853+
return self._nametowidget(name)
854+
except KeyError:
855+
return None
847856

848857
def tk_focusFollowsMouse(self):
849858
"""The widget under mouse will get automatically focus. Can not
@@ -1246,7 +1255,10 @@ def winfo_containing(self, rootX, rootY, displayof=0):
12461255
+ self._displayof(displayof) + (rootX, rootY)
12471256
name = self.tk.call(args)
12481257
if not name: return None
1249-
return self._nametowidget(name)
1258+
try:
1259+
return self._nametowidget(name)
1260+
except KeyError:
1261+
return None
12501262

12511263
def winfo_depth(self):
12521264
"""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)