https://github.com/python/cpython/commit/5c3555bdc56a8e110a7d366f8ac0a93cd082e90f commit: 5c3555bdc56a8e110a7d366f8ac0a93cd082e90f branch: main author: Serhiy Storchaka <[email protected]> committer: serhiy-storchaka <[email protected]> date: 2026-06-27T02:02:52+03:00 summary:
gh-38464: Make tkinter nametowidget() work with cloned menus (GH-152336) Map the auto-generated name of a cloned menu (a menu used as a menubar or a cascade) back to the original widget instead of raising KeyError. Co-authored-by: Claude Opus 4.8 <[email protected]> files: A Misc/NEWS.d/next/Library/2026-06-26-15-00-00.gh-issue-38464.yDPjKH.rst M Lib/test/test_tkinter/test_misc.py M Lib/tkinter/__init__.py diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 26c89403008d1b..4c003e697d23b8 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -390,6 +390,32 @@ def test_nametowidget(self): self.assertIs(self.root.nametowidget(str(b)), b) self.assertRaises(KeyError, self.root.nametowidget, '.nonexistent') + def test_nametowidget_menu_clone(self): + # A menu used as a menubar or cascade is cloned by Tk under an + # auto-generated name (each path component is the original name + # prefixed with one or more '#' clone markers). nametowidget() + # maps such a name back to the original widget (gh-38464). + menubar = tkinter.Menu(self.root) + filemenu = tkinter.Menu(menubar, tearoff=0) + menubar.add_cascade(label='File', menu=filemenu) + submenu = tkinter.Menu(filemenu, tearoff=0) + filemenu.add_cascade(label='More', menu=submenu) + self.root['menu'] = menubar + self.root.update_idletasks() + + originals = {menubar, filemenu, submenu} + clones = [] + def collect(parent): + for name in self.root.tk.splitlist( + self.root.tk.call('winfo', 'children', parent)): + clones.append(name) + collect(name) + collect('.') + # Every menu (originals and clones) resolves to an original widget. + self.assertTrue(any('#' in name for name in clones)) + for name in clones: + self.assertIn(self.root.nametowidget(name), originals) + def test_focus_methods(self): f = tkinter.Frame(self.root, width=150, height=100) f.pack() diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 1b218404624d90..897a1ba093c272 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -1800,7 +1800,16 @@ def nametowidget(self, name): for n in name: if not n: break - w = w.children[n] + try: + w = w.children[n] + except KeyError: + # Menu clones (a menu used as a menubar or a cascade) get + # auto-generated names where each path component is the + # original name prefixed with one or more '#' clone markers. + # Map such a name back to the original widget. + if not n.startswith('#'): + raise + w = w.children[n.rsplit('#', 1)[-1]] return w diff --git a/Misc/NEWS.d/next/Library/2026-06-26-15-00-00.gh-issue-38464.yDPjKH.rst b/Misc/NEWS.d/next/Library/2026-06-26-15-00-00.gh-issue-38464.yDPjKH.rst new file mode 100644 index 00000000000000..c1c272b97d4b0a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-26-15-00-00.gh-issue-38464.yDPjKH.rst @@ -0,0 +1,3 @@ +:meth:`!tkinter.Misc.nametowidget` now resolves the auto-generated names of +cloned menus (a menu used as a menubar or a cascade) back to the original +widget. _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
