Skip to content

Commit 8500b4c

Browse files
gh-38464: Make tkinter nametowidget() work with cloned menus
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>
1 parent bbf7786 commit 8500b4c

3 files changed

Lines changed: 39 additions & 1 deletion

File tree

Lib/test/test_tkinter/test_misc.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,32 @@ def test_nametowidget(self):
390390
self.assertIs(self.root.nametowidget(str(b)), b)
391391
self.assertRaises(KeyError, self.root.nametowidget, '.nonexistent')
392392

393+
def test_nametowidget_menu_clone(self):
394+
# A menu used as a menubar or cascade is cloned by Tk under an
395+
# auto-generated name (each path component is the original name
396+
# prefixed with one or more '#' clone markers). nametowidget()
397+
# maps such a name back to the original widget (gh-38464).
398+
menubar = tkinter.Menu(self.root)
399+
filemenu = tkinter.Menu(menubar, tearoff=0)
400+
menubar.add_cascade(label='File', menu=filemenu)
401+
submenu = tkinter.Menu(filemenu, tearoff=0)
402+
filemenu.add_cascade(label='More', menu=submenu)
403+
self.root['menu'] = menubar
404+
self.root.update_idletasks()
405+
406+
originals = {menubar, filemenu, submenu}
407+
clones = []
408+
def collect(parent):
409+
for name in self.root.tk.splitlist(
410+
self.root.tk.call('winfo', 'children', parent)):
411+
clones.append(name)
412+
collect(name)
413+
collect('.')
414+
# Every menu (originals and clones) resolves to an original widget.
415+
self.assertTrue(any('#' in name for name in clones))
416+
for name in clones:
417+
self.assertIn(self.root.nametowidget(name), originals)
418+
393419
def test_focus_methods(self):
394420
f = tkinter.Frame(self.root, width=150, height=100)
395421
f.pack()

Lib/tkinter/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1788,7 +1788,16 @@ def nametowidget(self, name):
17881788
for n in name:
17891789
if not n:
17901790
break
1791-
w = w.children[n]
1791+
try:
1792+
w = w.children[n]
1793+
except KeyError:
1794+
# Menu clones (a menu used as a menubar or a cascade) get
1795+
# auto-generated names where each path component is the
1796+
# original name prefixed with one or more '#' clone markers.
1797+
# Map such a name back to the original widget.
1798+
if not n.startswith('#'):
1799+
raise
1800+
w = w.children[n.rsplit('#', 1)[-1]]
17921801

17931802
return w
17941803

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:meth:`!tkinter.Misc.nametowidget` now resolves the auto-generated names of
2+
cloned menus (a menu used as a menubar or a cascade) back to the original
3+
widget.

0 commit comments

Comments
 (0)