Skip to content

Commit 5730c90

Browse files
miss-islingtonserhiy-storchakaclaude
authored
[3.14] gh-88758: Handle non-tkinter widgets in tkinter focus methods (GH-152337) (GH-152347)
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 ee1ae1d commit 5730c90

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

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

Lib/tkinter/__init__.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,10 @@ def focus_get(self):
817817
the focus."""
818818
name = self.tk.call('focus')
819819
if name == 'none' or not name: return None
820-
return self._nametowidget(name)
820+
try:
821+
return self._nametowidget(name)
822+
except KeyError:
823+
return None
821824

822825
def focus_displayof(self):
823826
"""Return the widget which has currently the focus on the
@@ -826,14 +829,20 @@ def focus_displayof(self):
826829
Return None if the application does not have the focus."""
827830
name = self.tk.call('focus', '-displayof', self._w)
828831
if name == 'none' or not name: return None
829-
return self._nametowidget(name)
832+
try:
833+
return self._nametowidget(name)
834+
except KeyError:
835+
return None
830836

831837
def focus_lastfor(self):
832838
"""Return the widget which would have the focus if top level
833839
for this widget gets the focus from the window manager."""
834840
name = self.tk.call('focus', '-lastfor', self._w)
835841
if name == 'none' or not name: return None
836-
return self._nametowidget(name)
842+
try:
843+
return self._nametowidget(name)
844+
except KeyError:
845+
return None
837846

838847
def tk_focusFollowsMouse(self):
839848
"""The widget under mouse will get automatically focus. Can not
@@ -1236,7 +1245,10 @@ def winfo_containing(self, rootX, rootY, displayof=0):
12361245
+ self._displayof(displayof) + (rootX, rootY)
12371246
name = self.tk.call(args)
12381247
if not name: return None
1239-
return self._nametowidget(name)
1248+
try:
1249+
return self._nametowidget(name)
1250+
except KeyError:
1251+
return None
12401252

12411253
def winfo_depth(self):
12421254
"""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)