diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 26c89403008..4c003e697d2 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 1b218404624..897a1ba093c 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 00000000000..c1c272b97d4 --- /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.