Skip to content

Commit 0290166

Browse files
miss-islingtonserhiy-storchakaclaude
authored
[3.15] gh-38464: Make tkinter nametowidget() work with cloned menus (GH-152336) (GH-152349)
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 90528ec commit 0290166

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

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

Lib/tkinter/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1724,7 +1724,16 @@ def nametowidget(self, name):
17241724
for n in name:
17251725
if not n:
17261726
break
1727-
w = w.children[n]
1727+
try:
1728+
w = w.children[n]
1729+
except KeyError:
1730+
# Menu clones (a menu used as a menubar or a cascade) get
1731+
# auto-generated names where each path component is the
1732+
# original name prefixed with one or more '#' clone markers.
1733+
# Map such a name back to the original widget.
1734+
if not n.startswith('#'):
1735+
raise
1736+
w = w.children[n.rsplit('#', 1)[-1]]
17281737

17291738
return w
17301739

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)