]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.15] gh-38464: Make tkinter nametowidget() work with cloned menus (GH-152336) ...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 26 Jun 2026 23:31:46 +0000 (01:31 +0200)
committerGitHub <noreply@github.com>
Fri, 26 Jun 2026 23:31:46 +0000 (23:31 +0000)
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 5c3555bdc56a8e110a7d366f8ac0a93cd082e90f)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Lib/test/test_tkinter/test_misc.py
Lib/tkinter/__init__.py
Misc/NEWS.d/next/Library/2026-06-26-15-00-00.gh-issue-38464.yDPjKH.rst [new file with mode: 0644]

index 11f40df21b1ebd0aa4522ce68fef839e0dc57d59..2a66ce01e0cf115a4e8ac81ddf80043fc2e79c4a 100644 (file)
@@ -388,6 +388,32 @@ class MiscTest(AbstractTkTest, unittest.TestCase):
         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()
index 6b2f21b9c0d55748468e15dfa0e74e14ac4eda11..9899d7622c672d526bef48856f11eba9352e63d2 100644 (file)
@@ -1724,7 +1724,16 @@ class Misc:
         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
 
diff --git a/Misc/NEWS.d/next/Library/2026-06-26-15-00-00.gh-issue-38464.yDPjKH.rst b/Misc/NEWS.d/next/Library/2026-06-26-15-00-00.gh-issue-38464.yDPjKH.rst
new file mode 100644 (file)
index 0000000..c1c272b
--- /dev/null
@@ -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.