From cee72326ffc2ec8d98f8d435c62e13a2f5c17edf Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 27 Jun 2026 01:31:09 +0200 Subject: [PATCH] [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 5fed5ce85d9c862673cc68294f757f345bbcc9b1) Co-authored-by: Serhiy Storchaka Co-authored-by: Claude Opus 4.8 --- Lib/test/test_tkinter/test_misc.py | 16 +++++++++++++++ Lib/tkinter/__init__.py | 20 +++++++++++++++---- ...6-06-26-15-30-00.gh-issue-88758.Qw7nLp.rst | 4 ++++ 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-26-15-30-00.gh-issue-88758.Qw7nLp.rst diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 4434ed08270..d7b0ee3d0c2 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -416,6 +416,22 @@ def test_focus_methods(self): self.root.update() self.assertIs(self.root.focus_get(), b) + def test_focus_methods_unresolvable(self): + # The focus may be on a widget that tkinter did not create and so + # cannot map to an instance (e.g. a torn-off menu). The focus + # methods return None instead of raising KeyError (gh-88758). + menu = tkinter.Menu(self.root, tearoff=1) + menu.add_command(label='Hello') + tearoff = self.root.tk.call('tk::TearOffMenu', str(menu), 0, 0) + self.addCleanup(self.root.tk.call, 'destroy', tearoff) + self.root.update() + self.assertRaises(KeyError, self.root.nametowidget, tearoff) + + self.root.tk.call('focus', '-force', tearoff) + self.root.update() + self.assertIsNone(self.root.focus_get()) + self.assertIsNone(self.root.focus_displayof()) + def test_grab(self): f = tkinter.Frame(self.root) f.pack() diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 7b3f4bea7ce..2dfe1b7bf82 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -803,7 +803,10 @@ def focus_get(self): the focus.""" name = self.tk.call('focus') if name == 'none' or not name: return None - return self._nametowidget(name) + try: + return self._nametowidget(name) + except KeyError: + return None def focus_displayof(self): """Return the widget which has currently the focus on the @@ -812,14 +815,20 @@ def focus_displayof(self): Return None if the application does not have the focus.""" name = self.tk.call('focus', '-displayof', self._w) if name == 'none' or not name: return None - return self._nametowidget(name) + try: + return self._nametowidget(name) + except KeyError: + return None def focus_lastfor(self): """Return the widget which would have the focus if top level for this widget gets the focus from the window manager.""" name = self.tk.call('focus', '-lastfor', self._w) if name == 'none' or not name: return None - return self._nametowidget(name) + try: + return self._nametowidget(name) + except KeyError: + return None def tk_focusFollowsMouse(self): """The widget under mouse will get automatically focus. Can not @@ -1222,7 +1231,10 @@ def winfo_containing(self, rootX, rootY, displayof=0): + self._displayof(displayof) + (rootX, rootY) name = self.tk.call(args) if not name: return None - return self._nametowidget(name) + try: + return self._nametowidget(name) + except KeyError: + return None def winfo_depth(self): """Return the number of bits per pixel.""" diff --git a/Misc/NEWS.d/next/Library/2026-06-26-15-30-00.gh-issue-88758.Qw7nLp.rst b/Misc/NEWS.d/next/Library/2026-06-26-15-30-00.gh-issue-88758.Qw7nLp.rst new file mode 100644 index 00000000000..31d567194fb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-26-15-30-00.gh-issue-88758.Qw7nLp.rst @@ -0,0 +1,4 @@ +:meth:`!tkinter.Misc.focus_get`, :meth:`!focus_displayof`, +:meth:`!focus_lastfor` and :meth:`!winfo_containing` now return ``None`` +instead of raising :exc:`KeyError` when the widget was not created by +:mod:`tkinter` (for example a torn-off menu).