Skip to content

Commit 7a835cb

Browse files
miss-islingtonserhiy-storchakaclaude
authored
[3.13] gh-38464: Make tkinter nametowidget() work with cloned menus (GH-152336) (GH-152351)
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 fd6562e commit 7a835cb

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
@@ -373,6 +373,32 @@ def test_nametowidget(self):
373373
self.assertIs(self.root.nametowidget(str(b)), b)
374374
self.assertRaises(KeyError, self.root.nametowidget, '.nonexistent')
375375

376+
def test_nametowidget_menu_clone(self):
377+
# A menu used as a menubar or cascade is cloned by Tk under an
378+
# auto-generated name (each path component is the original name
379+
# prefixed with one or more '#' clone markers). nametowidget()
380+
# maps such a name back to the original widget (gh-38464).
381+
menubar = tkinter.Menu(self.root)
382+
filemenu = tkinter.Menu(menubar, tearoff=0)
383+
menubar.add_cascade(label='File', menu=filemenu)
384+
submenu = tkinter.Menu(filemenu, tearoff=0)
385+
filemenu.add_cascade(label='More', menu=submenu)
386+
self.root['menu'] = menubar
387+
self.root.update_idletasks()
388+
389+
originals = {menubar, filemenu, submenu}
390+
clones = []
391+
def collect(parent):
392+
for name in self.root.tk.splitlist(
393+
self.root.tk.call('winfo', 'children', parent)):
394+
clones.append(name)
395+
collect(name)
396+
collect('.')
397+
# Every menu (originals and clones) resolves to an original widget.
398+
self.assertTrue(any('#' in name for name in clones))
399+
for name in clones:
400+
self.assertIn(self.root.nametowidget(name), originals)
401+
376402
def test_focus_methods(self):
377403
f = tkinter.Frame(self.root, width=150, height=100)
378404
f.pack()

Lib/tkinter/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1674,7 +1674,16 @@ def nametowidget(self, name):
16741674
for n in name:
16751675
if not n:
16761676
break
1677-
w = w.children[n]
1677+
try:
1678+
w = w.children[n]
1679+
except KeyError:
1680+
# Menu clones (a menu used as a menubar or a cascade) get
1681+
# auto-generated names where each path component is the
1682+
# original name prefixed with one or more '#' clone markers.
1683+
# Map such a name back to the original widget.
1684+
if not n.startswith('#'):
1685+
raise
1686+
w = w.children[n.rsplit('#', 1)[-1]]
16781687

16791688
return w
16801689

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)