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 <noreply@anthropic.com>
This commit is contained in:
Serhiy Storchaka 2026-06-27 02:02:52 +03:00 committed by GitHub
parent 5fed5ce85d
commit 5c3555bdc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 39 additions and 1 deletions

View file

@ -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()

View file

@ -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

View file

@ -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.