Skip to content

Commit 069c9c0

Browse files
miss-islingtonserhiy-storchakaclaude
authored
[3.14] gh-38464: Make tkinter nametowidget() work with cloned menus (GH-152336) (GH-152350)
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. (cherry picked from commit 5c3555b) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5730c90 commit 069c9c0

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
@@ -387,6 +387,32 @@ def test_nametowidget(self):
387387
self.assertIs(self.root.nametowidget(str(b)), b)
388388
self.assertRaises(KeyError, self.root.nametowidget, '.nonexistent')
389389

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

Lib/tkinter/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1700,7 +1700,16 @@ def nametowidget(self, name):
17001700
for n in name:
17011701
if not n:
17021702
break
1703-
w = w.children[n]
1703+
try:
1704+
w = w.children[n]
1705+
except KeyError:
1706+
# Menu clones (a menu used as a menubar or a cascade) get
1707+
# auto-generated names where each path component is the
1708+
# original name prefixed with one or more '#' clone markers.
1709+
# Map such a name back to the original widget.
1710+
if not n.startswith('#'):
1711+
raise
1712+
w = w.children[n.rsplit('#', 1)[-1]]
17041713

17051714
return w
17061715

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)