Skip to content

Commit cee7232

Browse files
miss-islingtonserhiy-storchakaclaude
authored
[3.13] gh-88758: Handle non-tkinter widgets in tkinter focus methods (GH-152337) (GH-152348)
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 7a835cb commit cee7232

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
@@ -416,6 +416,22 @@ def test_focus_methods(self):
416416
self.root.update()
417417
self.assertIs(self.root.focus_get(), b)
418418

419+
def test_focus_methods_unresolvable(self):
420+
# The focus may be on a widget that tkinter did not create and so
421+
# cannot map to an instance (e.g. a torn-off menu). The focus
422+
# methods return None instead of raising KeyError (gh-88758).
423+
menu = tkinter.Menu(self.root, tearoff=1)
424+
menu.add_command(label='Hello')
425+
tearoff = self.root.tk.call('tk::TearOffMenu', str(menu), 0, 0)
426+
self.addCleanup(self.root.tk.call, 'destroy', tearoff)
427+
self.root.update()
428+
self.assertRaises(KeyError, self.root.nametowidget, tearoff)
429+
430+
self.root.tk.call('focus', '-force', tearoff)
431+
self.root.update()
432+
self.assertIsNone(self.root.focus_get())
433+
self.assertIsNone(self.root.focus_displayof())
434+
419435
def test_grab(self):
420436
f = tkinter.Frame(self.root)
421437
f.pack()

Lib/tkinter/__init__.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -803,7 +803,10 @@ def focus_get(self):
803803
the focus."""
804804
name = self.tk.call('focus')
805805
if name == 'none' or not name: return None
806-
return self._nametowidget(name)
806+
try:
807+
return self._nametowidget(name)
808+
except KeyError:
809+
return None
807810

808811
def focus_displayof(self):
809812
"""Return the widget which has currently the focus on the
@@ -812,14 +815,20 @@ def focus_displayof(self):
812815
Return None if the application does not have the focus."""
813816
name = self.tk.call('focus', '-displayof', self._w)
814817
if name == 'none' or not name: return None
815-
return self._nametowidget(name)
818+
try:
819+
return self._nametowidget(name)
820+
except KeyError:
821+
return None
816822

817823
def focus_lastfor(self):
818824
"""Return the widget which would have the focus if top level
819825
for this widget gets the focus from the window manager."""
820826
name = self.tk.call('focus', '-lastfor', self._w)
821827
if name == 'none' or not name: return None
822-
return self._nametowidget(name)
828+
try:
829+
return self._nametowidget(name)
830+
except KeyError:
831+
return None
823832

824833
def tk_focusFollowsMouse(self):
825834
"""The widget under mouse will get automatically focus. Can not
@@ -1222,7 +1231,10 @@ def winfo_containing(self, rootX, rootY, displayof=0):
12221231
+ self._displayof(displayof) + (rootX, rootY)
12231232
name = self.tk.call(args)
12241233
if not name: return None
1225-
return self._nametowidget(name)
1234+
try:
1235+
return self._nametowidget(name)
1236+
except KeyError:
1237+
return None
12261238

12271239
def winfo_depth(self):
12281240
"""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)